// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "openzeppelin-contracts-08/token/ERC20/IERC20.sol";
import "openzeppelin-contracts-08/token/ERC20/ERC20.sol";
import 'openzeppelin-contracts-08/access/Ownable.sol';
contract DexTwo is Ownable {
address public token1;
address public token2;
constructor() {}
function setTokens(address _token1, address _token2) public onlyOwner {
token1 = _token1;
token2 = _token2;
}
function add_liquidity(address token_address, uint amount) public onlyOwner {
IERC20(token_address).transferFrom(msg.sender, address(this), amount);
}
function swap(address from, address to, uint amount) public {
require(IERC20(from).balanceOf(msg.sender) >= amount, "Not enough to swap");
uint swapAmount = getSwapAmount(from, to, amount);
IERC20(from).transferFrom(msg.sender, address(this), amount);
IERC20(to).approve(address(this), swapAmount);
IERC20(to).transferFrom(address(this), msg.sender, swapAmount);
}
function getSwapAmount(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(msg.sender, spender, amount);
SwappableTokenTwo(token2).approve(msg.sender, spender, amount);
}
function balanceOf(address token, address account) public view returns (uint){
return IERC20(token).balanceOf(account);
}
}
contract SwappableTokenTwo is ERC20 {
address private _dex;
constructor(address dexInstance, string memory name, string memory symbol, uint initialSupply) ERC20(name, symbol) {
_mint(msg.sender, initialSupply);
_dex = dexInstance;
}
function approve(address owner, address spender, uint256 amount) public {
require(owner != _dex, "InvalidApprover");
super._approve(owner, spender, amount);
}
}
우리의 mission은 token1, token2를 모두 추출해야 한다.
저번 dex랑 비슷하지만 swap부분에서 코드가 누락된 부분이 있다.
require((from == token1 && to == token2) || (from == token2 && to == token1), "Invalid tokens");
이 부분이 누락되었다. 즉 token을 우리가 새로 만들 수 있다는 말이다.
ERC20에서 우리가 토큰을 새로 추가해서 뺄 수도 있다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract Attack is ERC20 {
constructor(uint256 initialSupply) ERC20("AkToken", "ATK") {
_mint(msg.sender, initialSupply);
}
}
그래서 remix를 이용하여 개인 토큰을 하나 만든다. 그리고 DEX2 Contract에 토큰을 전송하고, approve를 하면 준비는 끝났다.
그럼 얼마나 보내고 얼마나 가지고 있어야 할까??
getSwapAmount() 함수의 공식을 보면 amount * to.balanceOf (this) / from.balanceOf (this)다. 여기서 DEX2에 있는 자원은 token1 100개, token2 100개이다.
DEX2 contract에 있는 ATK 코인이 n개, 내가 소유하고 있는 ATK코인이 k개라고 가정하면, 100 * 100/n = 100, (k-100) * 100/(n+100) = 100 2개의 연산이 나온다. 계산을 해보면 100/n = 1, n = 100 (k-100) * 0.5 = 100, k-100 = 200, k = 300
n=100, k=300이라는 결과가 나온다. 그래서 initialSupply를 400으로 하고 100을 DEX2 컨트랙트에 주고 300을 가지고 token을 훔치면 된다.
표로 나타내면 위 그림과 같이 진행된다. 실제로 저렇게 되는지 확인해 보자.
기본 세팅을 하고 시작하면 된다.
이렇게 token1한테 100을 주고 token2한테 200을 주면 DEX2의 잔고가 0이 되고 atk 토큰이 400이 된다.