1. 문제

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;
    }
}

2. 코드 분석

cast 4byte 0xdd62ed3e
> allowance(address,address)

cast 4byte 0x23b872dd
> transferFrom(address,address,uint256)

0xdd62ed3e → allowance(address,address)

0x23b872dd → transferFrom(address,address,uint256)

StakeWETH()

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가지 변수의 값은 증가하게 된다.