1. Fallback

Difficulty: 1/10
Level: https://ethernaut.openzeppelin.com/level/0x9CB391dbcD447E645D6Cb55dE6ca23164130D008

先看通关要求:

  1. you claim ownership of the contract
  2. you reduce its balance to 0

再分析合约代码,要成为合约的owner,有两种方式:

第一种

1
2
3
4
5
6
...
contributions[msg.sender] += msg.value;
if(contributions[msg.sender] > contributions[owner]) {
owner = msg.sender;
}
...

现在合约的owner初始化写入 contributions[msg.sender] = 1000 * (1 ether);,也就是说需要传入大于 owner 的数量,但是 contribute 函数中已定义 require(msg.value < 0.001 ether); 所以此方法行不通。

第二种

最下方有一段跟其他函数不一样的代码,前面没有 function关键字,而是以 receive() 开头。这里有一个知识点 fallback function:

The fallback function is executed on a call to the contract if none of the other functions match the given function signature, or if no data was supplied at all and there is no receive Ether function. The fallback function always receives data, but in order to also receive Ether it must be marked payable.

也就是说,如果我们调用一个合约时函数签名不一致,或者函数名称不正确,就会直接运行这个 fallback function

分析这个合约里的 fallback function 的代码:

1
2
3
4
receive() external payable {
require(msg.value > 0 && contributions[msg.sender] > 0);
owner = msg.sender;
}

只需要达成 msg.value > 0contributions[msg.sender] > 0 两个条件,msg.sender 就会被设定成 owner。

msg.value 可以在调用合约时控制。

而另外一个条件需要再分析下代码:contributions 是一个 mapping,需要写入数据才能调用,而在 contribute 函数中有写入数据能力,分析 contribute 函数可知,以msg.value < 0.001 ether 调用 contribute 函数,可令 contributions[msg.sender] > 0。打开 Console(F12),输入:

1
2
> contract.contribute({value: toWei("0.00001")})

由于1 ether= 10^18^ wei,这里使用了预设的 toWei 函数将 0.00001 ether 转换成 wei 单位发送。

待交易确认后,再调用 fallback

1
> contract.sendTransaction({value: toWei("0.00001")})

此时 owner 已经转为 msg.sender

再调用 withdraw 函数把balance 归零:

1
2
> contract.withdraw()

最后提交,本关完成。