Skip to content

Commit

Permalink
Merge pull request #19 from lightclient/add-2935
Browse files Browse the repository at this point in the history
Add EIP-2935 system contract impl
  • Loading branch information
lightclient authored Oct 27, 2024
2 parents c9f9819 + d8dcf8d commit 429daf0
Show file tree
Hide file tree
Showing 6 changed files with 272 additions and 1 deletion.
5 changes: 5 additions & 0 deletions build-wrapper
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ set -euf -o pipefail
SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]:-$0}"; )" &> /dev/null && pwd 2> /dev/null; )";

BEACONROOT_BYTECODE="$(geas "src/beacon_root/main.eas")"
EXECHASH_BYTECODE="$(geas "src/execution_hash/main.eas")"
WITHDRAWALS_BYTECODE="$(geas "src/withdrawals/main.eas")"
CONSOLODATIONS_BYTECODE="$(geas "src/consolidations/main.eas")"
FAKE_EXPO_BYTECODE="$(geas "src/common/fake_expo_test.eas")"
Expand All @@ -12,6 +13,10 @@ sed \
-e "s/@bytecode@/$BEACONROOT_BYTECODE/" \
"$SCRIPT_DIR/test/BeaconRoot.t.sol.in" > "$SCRIPT_DIR/test/BeaconRoot.t.sol"

sed \
-e "s/@bytecode@/$EXECHASH_BYTECODE/" \
"$SCRIPT_DIR/test/ExecutionHash.t.sol.in" > "$SCRIPT_DIR/test/ExecutionHash.t.sol"

sed \
-e "s/@bytecode@/$WITHDRAWALS_BYTECODE/" \
-e "s/@bytecode_expo@/$FAKE_EXPO_BYTECODE/" \
Expand Down
4 changes: 4 additions & 0 deletions scripts/addr.sh
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ case $1 in
echo "searching for consolidations deployment data "
nick search --score=$score --initcode="0x$(geas src/consolidations/ctor.eas)" --prefix=0x0000 --suffix=0xbbbb
;;
exechash|e|2935)
echo "searching for execution hash deployment data "
nick search --score=$score --initcode="0x$(geas src/execution_hash/ctor.eas)" --prefix=0x0000 --suffix=0xcccc
;;
*)
echo "Invalid option. Usage: $0 {withdrawals|consolidations}"
;;
Expand Down
12 changes: 12 additions & 0 deletions src/execution_hash/ctor.eas
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
;; Copy and return code.
push @.end - @.start
dup1
push @.start
push0
codecopy
push0
return

.start:
#assemble "main.eas"
.end:
102 changes: 102 additions & 0 deletions src/execution_hash/main.eas
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
;; ┏┓┏┓┏┓┏━
;; ┏┛┗┫ ┫┗┓┏┓┏┏┳┓
;; ┗━┗┛┗┛┗┛┗┻┛┛┗┗
;;
;; This is an implementation of EIP-2935's predeploy contract.
;;
;; The contract implements a ring buffer to create bounded execution block
;; hash lookup.

;; ----------------------------------------------------------------------------
;; MACROS ---------------------------------------------------------------------
;; ----------------------------------------------------------------------------

;; BUFLEN returns the HISTORY_BUFFER_LENGTH as defined in the EIP.
#define BUFLEN 8191

;; SYSADDR is the address which calls the contract to submit a new root.
#define SYSADDR 0xfffffffffffffffffffffffffffffffffffffffe

;; do_revert sets up and then executes a revert(0,0) operation.
#define %do_revert() {
push0 ;; [0]
push0 ;; [0, 0]
revert ;; []
}

;; ----------------------------------------------------------------------------
;; MACROS END -----------------------------------------------------------------
;; ----------------------------------------------------------------------------

.start:
;; Protect the submit routine by verifying the caller is equal to
;; sysaddr().
caller ;; [caller]
push20 SYSADDR ;; [sysaddr, caller]
eq ;; [sysaddr == caller]
jumpi @submit ;; []

;; Fallthrough if addresses don't match -- this means the caller intends
;; to read a root.

;; Verify input is 32 bytes long.
push1 32 ;; [32]
calldatasize ;; [calldatasize, 32]
eq ;; [calldatasize == 32]

;; Jump to continue if length-check passed, otherwise revert.
jumpi @load ;; []
%do_revert() ;; []

load:
;; Check if input is requesting a block hash greater than current block
;; number.
push 0 ;; [0]
calldataload ;; [input]
push 1 ;; [1, input]
number ;; [number, 1, input]
sub ;; [number-1, input]
dup2 ;; [input, number-1, input]
gt ;; [input > number-1, input]
jumpi @throw ;; [input]

;; Check if the input is requesting a block hash before the earliest available
;; hash currently. Since we've verfied that input <= number - 1, it's safe to
;; check the following:
;; number - 1 - input <= BUFLEN, which also equals: number - input < BUFLEN
dup1 ;; [input, input]
number ;; [number, input, input]
sub ;; [number - input, input]
push BUFLEN+1 ;; [buflen, number - input, input]
lt ;; [buflen < number - input, input]
jumpi @throw ;; [input]

;; Load the hash.
push BUFLEN ;; [buflen, input]
swap1 ;; [input, buflen]
mod ;; [input % buflen]
sload ;; [hash]

;; Load into memory and return.
push 0 ;; [0, hash]
mstore ;; []
push 32 ;; [32]
push 0 ;; [0, 32]
return ;; []

throw:
;; Reverts current execution with no return data.
pop
%do_revert()

submit:
push 0 ;; [0]
calldataload ;; [in]
push BUFLEN ;; [buflen, in]
push 1 ;; [1, buflen, in]
number ;; [number, 1, buflen, in]
sub ;; [number-1, buflen, in]
mod ;; [number-1 % buflen, in]
sstore

stop
148 changes: 148 additions & 0 deletions test/ExecutionHash.t.sol.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "forge-std/Test.sol";
import "../src/Contract.sol";

address constant addr = 0x000000000000000000000000000000000000aaaa;
address constant sysaddr = 0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE;
uint256 constant buflen = 8191;
bytes32 constant hash = hex"88e96d4537bea4d9c05d12549907b32561d3bf31f45aae734cdc119f13406cb6";

function lastBlockNumber() view returns (bytes32) {
return bytes32(uint256(block.number)-1);
}

function hash_idx() view returns (bytes32) {
return bytes32(uint256(lastBlockNumber()) % buflen);
}

contract ContractTest is Test {
address unit;

function setUp() public {
vm.etch(addr, hex"@bytecode@");
unit = addr;
}

// testRead verifies the contract returns the expected execution hash.
function testExecRead() public {
// Store hash at expected indexes.
vm.store(unit, hash_idx(), hash);

// Read hash associated with current timestamp.
(bool ret, bytes memory data) = unit.call(bytes.concat(lastBlockNumber()));
assertTrue(ret);
assertEq(data, bytes.concat(hash));
}

function testReadBadCalldataSize() public {
// Store hash at expected indexes.
vm.store(unit, hash_idx(), hash);

// Call with 0 byte arguement.
(bool ret, bytes memory data) = unit.call(hex"");
assertFalse(ret);
assertEq(data, hex"");

// Call with 31 byte arguement.
(ret, data) = unit.call(hex"00000000000000000000000000000000000000000000000000000000001337");
assertFalse(ret);
assertEq(data, hex"");

// Call with 33 byte arguement.
(ret, data) = unit.call(hex"000000000000000000000000000000000000000000000000000000000000001337");
assertFalse(ret);
assertEq(data, hex"");
}

function testReadBadBlockNumbers() public {
// Set reasonable block number.
vm.roll(21053500);
uint256 number = block.number-1;

// Store hash at expected indexes.
vm.store(unit, hash_idx(), hash);

// Wrap around buflen once forward.
(bool ret, bytes memory data) = unit.call(bytes.concat(bytes32(number+buflen)));
assertFalse(ret);
assertEq(data, hex"");

// Wrap around buflen once backward.
(ret, data) = unit.call(bytes.concat(bytes32(number-buflen-1)));
assertFalse(ret);
assertEq(data, hex"");

// Block number zero should fail.
(ret, data) = unit.call(bytes.concat(bytes32(0)));
assertFalse(ret);
assertEq(data, hex"");
}

// testUpdate verifies the set functionality of the contract.
function testUpdate() public {
// Simulate pre-block call to set hash.
vm.prank(sysaddr);
(bool ret, bytes memory data) = unit.call(bytes.concat(hash));
assertTrue(ret);
assertEq(data, hex"");

// Verify hash.
bytes32 got = vm.load(unit, hash_idx());
assertEq(got, hash);
}

// testRingBuffers verifies the integrity of the ring buffer is maintained
// as the write indexes loop back to the start and begin overwriting
// values.
function testRingBuffers() public {
// Set reasonable block number.
vm.roll(21053500);

for (uint256 i = 0; i < 10000; i += 1) {
bytes32 pbbr = bytes32(i*1337);

// Simulate pre-block call to set hash.
vm.prank(sysaddr);
(bool ret, bytes memory data) = unit.call(bytes.concat(pbbr));
assertTrue(ret);
assertEq(data, hex"");

// Call contract as normal account to get exeuction hash associated
// with current timestamp.
(ret, data) = unit.call(bytes.concat(lastBlockNumber()));
assertTrue(ret);
assertEq(data, bytes.concat(pbbr));

// Skip forward 1 block.
vm.roll(block.number+1);
}
}


// testHistoricalReads verifies that it is possible to read all previously
// saved values in the beacon hash contract.
function testHistoricalReads() public {
uint256 start = 1;
vm.roll(start);

// Saturate storage with fake hashs.
for (uint256 i = 0; i < buflen; i += 1) {
bytes32 pbbr = bytes32(i*1337);
vm.prank(sysaddr);
(bool ret, bytes memory data) = unit.call(bytes.concat(pbbr));
assertTrue(ret);
assertEq(data, hex"");
vm.roll(block.number+1);
}

// Attempt to read all values in same block context.
for (uint256 i = 0; i < buflen; i += 1) {
bytes32 num = bytes32(uint256(start+i-1));
(bool ret, bytes memory got) = unit.call(bytes.concat(num));
assertTrue(ret);
assertEq(got, bytes.concat(bytes32(i*1337)));
}
}
}

0 comments on commit 429daf0

Please sign in to comment.