Stake 컨트랙트는 네이티브 ETH와 ERC20 WETH를 같은 1:1 가치로 스테이킹할 수 있다고 가정합니다.
당신의 목표는 이 컨트랙트를 고갈(drain) 시킬 수 있는지 확인하는 것입니다.
클리어 조건
1. Stake 컨트랙트의 ETH 잔액이 0보다 커야할 것
2. totalStaked 상태값이 Stake 컨트랙트의 ETH 잔액보다 커야할 것
3. Stakers[player]의 값이 true가 되야할 것
4. UserStake[player]의 값이 0이어야 할 것
힌트
[ERC-20](<https://github.com/ethereum/ercs/blob/master/ERCS/erc-20.md>)
[openzeppelin-contracts](<https://github.com/OpenZeppelin/openzeppelin-contracts>)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Stake {
uint256 public totalStaked;
mapping(address => uint256) public UserStake;
mapping(address => bool) public Stakers;
address public WETH;
constructor(address _weth) payable{
totalStaked += msg.value;
WETH = _weth;
}
function StakeETH() public payable {
require(msg.value > 0.001 ether, "Don't be cheap");
totalStaked += msg.value;
UserStake[msg.sender] += msg.value;
Stakers[msg.sender] = true;
}
function StakeWETH(uint256 amount) public returns (bool){
require(amount > 0.001 ether, "Don't be cheap");
(,bytes memory allowance) = WETH.call(abi.encodeWithSelector(0xdd62ed3e, msg.sender,address(this)));
require(bytesToUint(allowance) >= amount,"How am I moving the funds honey?");
totalStaked += amount;
UserStake[msg.sender] += amount;
(bool transfered, ) = WETH.call(abi.encodeWithSelector(0x23b872dd, msg.sender,address(this),amount));
Stakers[msg.sender] = true;
return transfered;
}
function Unstake(uint256 amount) public returns (bool){
require(UserStake[msg.sender] >= amount,"Don't be greedy");
UserStake[msg.sender] -= amount;
totalStaked -= amount;
(bool success, ) = payable(msg.sender).call{value : amount}("");
return success;
}
function bytesToUint(bytes memory data) internal pure returns (uint256) {
require(data.length >= 32, "Data length must be at least 32 bytes");
uint256 result;
assembly {
result := mload(add(data, 0x20))
}
return result;
}
}
cast 4byte 0xdd62ed3e
> allowance(address,address)
cast 4byte 0x23b872dd
> transferFrom(address,address,uint256)
0xdd62ed3e → allowance(address,address)
0x23b872dd → transferFrom(address,address,uint256)
function StakeWETH(uint256 amount) public returns (bool){
require(amount > 0.001 ether, "Don't be cheap");
(,bytes memory allowance) = WETH.call(abi.encodeWithSelector(0xdd62ed3e, msg.sender,address(this)));
require(bytesToUint(allowance) >= amount,"How am I moving the funds honey?");
totalStaked += amount;
UserStake[msg.sender] += amount;
(bool transfered, ) = WETH.call(abi.encodeWithSelector(0x23b872dd, msg.sender,address(this),amount));
Stakers[msg.sender] = true;
return transfered;
}
(bool transfered, ) = WETH.call(abi.encodeWithSelector(0x23b872dd, msg.sender,address(this),amount));
StakeWETH() 함수를 살펴보면 transferFrom() 함수를 호출하고 이 과정에서 WETH의 잔액이 부족하여 false를 반환해도 해당 로직을 revert하는 부분이 존재하지 않는다.
즉, transfered 값을 require 하는 로직이 존재하지 않는다.
따라서 stake에 실패하여도 아래 3가지 변수의 값은 증가하게 된다.