function solve() external {
        if (token.balanceOf(address(this)) == 1) {
            solved = 1;
        }
    }

solve() 함수를 보면 Level contract내부에 있는 token의 잔액이 1이 되면 문제가 풀린다.

이더를 가져올 수 있는 방법은 permitAndTransfer() 함수 내부에 아래 로직이 있기 때문에 이를 이용하면 될 것이다.

_approve(owner, spender, value);
_transfer(owner, spender, value);

여기서 중간에 revert가 나지않고 위 로직까지 도달하려면 _verifySignatureType1 또는 _verifySignatureType2

의 값 중 하나를 true로 만들어야 한다.

this.ecrecover(hash, v, r, s) 로직에 의해 signer을 복구하게 되는데 이때 개인키가 필요하게 된다.

여기서 type1은 owner와 비교하는데 owner는 컨트렉트이기 때문에 개인키가 존재하지 않고 따라서 type2를 true로 만들어보자

type2에 존재하는 signer == _msgSender() 를 보면 signer을 내 개인키를 가지고 서명을 생성하고, 내가 직접 트랜잭션을 호출하면 true조건이 만족될거 같다.

function permitAndTransfer(
        string memory note,
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    )

위 함수의 파라미터를 잘 채우는게 이번 문제의 핵심이다.

여기서 v, r, s변수의 값을 구하려면 크게 DOMAIN_SEPARATOR, structHash 가 필요하다 그 중, structHash 내부에 들어갈 값을 알아보면 아래와 같다

TYPEHASH 문제에 나와있는 상수값 사용
origin 이 문제에서는 USER_ADDRESS를 넣어 위에서 언급한대로 true 조건을 만족 시킴
owner CONTRACT_ADDRESS
spender USER_ADDRESS
value Level 컨트랙트에 있는 token 의 잔액 -1로 설정하여 1개의 이더만 남기도록 한다
nonce 현재 넌스 +2(type1에서 넌스가 하나 사용되기 때문에 +2를 해준다)
deadline 적당히 지금 시간보다 큰 값으로 설정

위 값과 개인키를 이용해 v, r, s 값을 구하고 최종적으로 아래 값들을 이용해 permitAndTransfer() 함수를 호출한다.

note 아무값이나 넣어도 상관 x
owner CONTRACT_ADDRESS
spender USER_ADDRESS
value Level 컨트랙트에 있는 token 의 잔액 -1로 설정하여 1개의 이더만 남기도록 한다
deadline 적당히 지금 시간보다 큰 값으로 설정
v 위에서 구한 값 사용
r 위에서 구한 값 사용
s 위에서 구한 값 사용

Payload