Inspired by OpenZeppelin's Ethernaut, Preservation Level
This contract utilizes a library to store two different times for two different timezones. The constructor creates two instances of the library for each time to be stored. The goal of this game is for you to claim ownership of the instance you are given.
Hint:
- 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. - Understanding what it means for
delegatecall
to be context-preserving. - Understanding how storage variables are stored and accessed.
- Understanding how casting works between different data types.
delegatecall
low level operationlibrary
vscontract
- Layout of State Variables in Storage
This game requires you to combine knowledge from Delegation Attack and Privacy Attack to claim ownership of the contract.
Delegate
call is a special, low level function call intended to invoke functions from another, often library, contract.- If Contract A makes a
delegatecall
to Contract B, it allows Contract B to freely mutate its storage A, given Contract B’s relative storage reference pointers.
Hint: if Contract A invokes Contract B, and you can control Contract B, you can easily mutate the state of Contract A.
- Ethereum allots 32-byte sized storage slots to store state. Slots start at index
0
and sequentially go up to2²⁵⁶
slots. - Basic datatypes are laid out contiguously in storage starting from position
0
, then1
, until2²⁵⁶-1
. - If the combined size of sequentially declared data is less than 32 bytes, then the sequential data points are packed into a single storage slot to optimize space and gas.
Hint: If you can match up storage data locations between Contract A and Contract B, you can precisely manipulate the desired variables in Contract A.
- Ideally, libraries should not store state.
- When creating libraries, use
library
, notcontract
, to ensure libraries will not modify caller storage data when caller usesdelegatecall
. Use higher level function calls to inherit from libraries, especially when you i) don’t need to change contract storage and ii) do not care about gas control.
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;
contract Preservation {
// public library contracts
address public timeZone1Library;
address public timeZone2Library;
address public owner;
uint256 storedTime;
// Sets the function signature for delegatecall
bytes4 constant setTimeSignature = bytes4(keccak256("setTime(uint256)"));
constructor(address _timeZone1LibraryAddress, address _timeZone2LibraryAddress) public {
timeZone1Library = _timeZone1LibraryAddress;
timeZone2Library = _timeZone2LibraryAddress;
owner = msg.sender;
}
// set the time for timezone 1
function setFirstTime(uint256 _timeStamp) public {
timeZone1Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp));
}
// set the time for timezone 2
function setSecondTime(uint256 _timeStamp) public {
timeZone2Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp));
}
}
// Simple library contract to set the time
contract LibraryContract {
// stores a timestamp
uint256 storedTime;
function setTime(uint256 _time) public {
storedTime = _time;
}
}
Skip if you have already installed.
npm install -g truffle
yarn install
truffle develop
test
You should take ownership of the target contract successfully.
truffle(develop)> test
Using network 'develop'.
Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile.
Contract: Hacker
√ should claim ownership (393ms)
1 passing (457ms)