// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract King {
address king;
uint256 public prize;
address public owner;
constructor() payable {
owner = msg.sender;
king = msg.sender;
prize = msg.value;
}
receive() external payable {
require(msg.value >= prize || msg.sender == owner);
payable(king).transfer(msg.value);
king = msg.sender;
prize = msg.value;
}
function _king() public view returns (address) {
return king;
}
}
이 문제는 이 시스템을 붕괴(?) 시키는 것 입니다. 시스템이 더 이상 정상적인 작동을 못하도록 하면 되는 것입니다.
컨트랙트를 살펴보면 생성됨과 동시에 owner, king, prize가 정해지는걸 확인할 수 있습니다. 그리고 receive 함수를 보면 owner이거나 prize보다 높은 ETH를 전송한다면 기존의 king에게 해당 ETH를 전송하고 새로운 king이 될 수 있습니다.
시스템을 붕괴시킨다 == 더 이상 정상적인 작동을 불가능하게 만든다.
어떻게 하면 해당 컨트랙트가 더 이상 정상적으로 작동하지 못하게 할 수 있을까요...
이 시스템은 누군가가 왕이 되고 새로운 왕이 나타난다면 보상을 받고 물러나도록 되어있습니다.
이게 문제 컨트랙트의 거의 유일한 기능이라 할 수 있습니다.
그렇다면 이 기능이 더 이상 작동하지 못하도록 하면 됩니다.
2가지 방법이 존재합니다.
첫 번째 방법은 현실성이 없습니다.. 물론 할 수 있긴 하지만 문제의 의도랑은 조금 맞지 않는거 같습니다. 돈자랑 하는 곳이 아니기 때문입니다..!
그렇다면 어떻게 prize를 받는 것을 거부할 수 있을까요.
일단 king이 EOA라면 거부하지 못합니다. EOA는 돈을 받을때는 특정 조건을 걸지 못하기 때문입니다. 프로그래밍이 불가능합니다..!(metamask 기준)
하지만 CA 즉 컨트랙트는 해당 조건을 프로그래밍할 수 있습니다. CA는 receive, fallback 같은 함수로 ETH를 받을 때 특정 조건을 걸거나 구현하지 않는다면 안받을 수 있습니다..!
기존에는 fallback만 존재했지만 현재에는 receive와 fallback으로 나뉘었습니다.
두 함수 모두 external 키워드가 필수입니다.
- fallback: 현재는 컨트랙트에 존재하지 않는 함수가 호출될 때 실행되고 있습니다. 존재하지 않는 함수에 대한 예외 처리이기 때문에 payable를 사용해도, 안해도 됩니다.(함수에서 ETH를 보낼 때, calldata 존재)
- receive: payable 키워드를 반드시 사용해야 합니다. 이더를 받을 때 호출되며 call, send, transfer 같은 전송 함수에 의해 실행됩니다. (calldata 없이 순수하게 ETH 전송 시 호출)
그렇다면 공격 컨트랙트를 작성하고 위 함수를 구현하지 않거나 내부에 revert()로 트랜잭션을 거부하는 방법을 사용하면 됩니다..!
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Attack {
constructor(address payable target) payable {
(bool success,) = target.call{value: msg.value}("");
require(success, "failed");
}
receive() external payable {
revert();
}
}
contract.prize()를 호출해 prize를 알아내고 해당 가격 이상의 prize를 같이 전송해주면 됩니다.
저는 좀 더 알아보기 쉽게 receive 내부에서 revert하고 있도록 구현했습니다.
이외에도 공격함수를 따로 구현하거나 King instance를 만들거나 여러가지 방법으로 공격 컨트랙트를 작성 가능합니다.
receive, fallback 함수는 컨트랙트의 특성에 따라서 잘 사용해야 합니다. 가스비 제한도 존재하기 때문에 내부에 너무 많은 동작을 집어 넣을 시 컨트랙트가 ETH를 받지 못할 수도 있습니다.
그리고 문제의 컨트랙트에서는 king을 오직 EOA만 가능하게 하던가 아니라면 전송할 때 상대 컨트랙트에 ETH를 받을 수 있는 장치가 있는지 확인하는 safeTransfer 같은 함수를 사용하는 것이 좋아보입니다.