How to safely push payments in smart contracts: Nouns DAO and Ethernaut’s King challenge

Peter Conerly
Northwest NFTs
Published in
3 min readNov 3, 2021

While refreshing Nouns DAO and hoping to snipe a noun for less than 10ETH, I realized that their bidding structure reminded me of Ethernaut’s King’s challenge. Ethernaut is a hacking wargame on the Ethereum blockchain, and has twenty-five challenges that showcase the potential exploits in ethereum smart contracts. Before you get excited, there’s no exploit on Nouns DAO. If there was I wouldn’t be writing this post, I would be hijacking nouns through an anonymous wallet and a Russian VPN exit node.

The King challenge hands the user a “King of the hill” contract; users can bid to be the King. When a new user has outbid the previous King, the previous King has the bid sent to them, and the new King is seated. The key to the exploit is that Ethereum’s transfers of payment are not guaranteed to be successful, and King doesn’t handle that case. The King challenge is based on the real-life exploit of King of the Ether.

How do these contracts work? NounsAuctionHouse and King follow a similar pattern. Here’s King’s code:

King.sol

One of the first things I was told about Ethereum smart contract development is that push-payments are dangerous, and a pull-model is preferred. This is good general advice for beginners, but it’s good to understand why pull is favored over push.

The King challenge is attacked by making a smart contract submit the bid, and then failing on the transfer function. One way to fail the transfer is not to have a valid receive function, by omitting it entirely from the contract or not giving it the correct function interface. This is called the Unchecked Call attack.

The second method to make an incoming transfer fail is with a gas limit denial of service attack or “gas attack”. When an Ethereum smart contract transfers eth using `call`, there is no default gas limit, and before Solidity v7 the `transfer` and `send` functions also didn’t have default gas limits. A malicious receive function can burn gas with useless transactions: see my solution below.

KingMeForever.sol

Sadly, ethernaut’s King validator didn’t recognize my gas attack solution! I’ve submitted a fix to Ethernauts to fix the King validator.

How does NounsAuctionHouse safely push a bid back to a user?

NounsAuctionHouse.sol, lines 100–136

NounsAuctionHouse calls `_safeTransferETHWithFallback`, let’s look at that:

NounsAuctionHouse.sol, lines 243–260

The contract attempts to transfer eth with `call`, and includes a gas limit. If a receive function uses up all 30,000 gas allocated the call fails, and thus a gas limit denial of service attack is neutralized. If the transaction fails for any reason the balance is converted into wrapped ETH and then transferred as an ERC20 token. The NounsAuctionHouse contract authors know they can trust the Wrapped ETH contract. Transferring Wrapped ETH doesn’t actually move ETH, it’s simply an accounting change to a WETH data structure, which is also verified to be safe.

One remaining issue is that if a smart contract won a bid on NounsAuctionHouse, but then was out-bid, there’s a potential problem. If the smart contract’s receive function failed, NounsAuctionHouse would transfer them WETH tokens via ERC20. If the smart contract wasn’t set up to call the Wrapped ETH’s contract’s `withdraw()` function, their refunded bid would be unrecoverable.

OpenZeppelin’s advice to favor pull over push is still relevant today, but pushing can be done safely if needed by contract.

If you are working on an NFT project and need smart contract or web development support, contact us at NorthwestNFTs.com

--

--