Skip to content

Commit

Permalink
add 4788 beacon contract (converted from etk)
Browse files Browse the repository at this point in the history
  • Loading branch information
lightclient committed Aug 20, 2024
1 parent ee61e80 commit 75e602a
Show file tree
Hide file tree
Showing 7 changed files with 329 additions and 8 deletions.
17 changes: 11 additions & 6 deletions build-wrapper
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,27 @@ set -euf -o pipefail

SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]:-$0}"; )" &> /dev/null && pwd 2> /dev/null; )";

WITHDRAWAL_BYTECODE="$(geas "src/withdrawals/main.eas")"
CONSOLODATION_BYTECODE="$(geas "src/consolidations/main.eas")"
BEACONROOT_BYTECODE="$(geas "src/beacon_root/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")"

sed \
-e "s/@bytecode@/$WITHDRAWAL_BYTECODE/" \
-e "s/@bytecode2@/$FAKE_EXPO_BYTECODE/" \
-e "s/@bytecode@/$BEACONROOT_BYTECODE/" \
"$SCRIPT_DIR/test/BeaconRoot.t.sol.in" > "$SCRIPT_DIR/test/BeaconRoot.t.sol"

sed \
-e "s/@bytecode@/$WITHDRAWALS_BYTECODE/" \
-e "s/@bytecode_expo@/$FAKE_EXPO_BYTECODE/" \
"$SCRIPT_DIR/test/Withdrawal.t.sol.in" > "$SCRIPT_DIR/test/Withdrawal.t.sol"

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

sed \
-e "s/@bytecode@/$CONSOLODATION_BYTECODE/" \
-e "s/@bytecode2@/$FAKE_EXPO_BYTECODE/" \
-e "s/@bytecode@/$CONSOLODATIONS_BYTECODE/" \
-e "s/@bytecode_expo@/$FAKE_EXPO_BYTECODE/" \
"$SCRIPT_DIR/test/Consolidation.t.sol.in" > "$SCRIPT_DIR/test/Consolidation.t.sol"

forge "$@" --evm-version shanghai
4 changes: 4 additions & 0 deletions scripts/addr.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ default_score=5
score=${2:-$default_score}

case $1 in
beaconroot|b|4788)
echo "searching for beacon root deployment data "
nick search --score=$score --initcode="0x$(geas src/beacon_root/ctor.eas)" --prefix=0xbeac02 --suffix=0x0000
;;
withdrawals|wxs|7002)
echo "searching for withdrawals deployment data "
nick search --score=$score --initcode="0x$(geas src/withdrawals/ctor.eas)" --prefix=0x0000 --suffix=0xaaaa
Expand Down
12 changes: 12 additions & 0 deletions src/beacon_root/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:
138 changes: 138 additions & 0 deletions src/beacon_root/main.eas
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
;; __ ___________ ____
;; / // /__ ( __ )( __ )____ __________ ___
;; / // /_ / / __ / __ / __ `/ ___/ __ `__ \
;; /__ __// / /_/ / /_/ / /_/ (__ ) / / / / /
;; /_/ /_/\____/\____/\__,_/____/_/ /_/ /_/
;;
;; This is an implementation of EIP-4788's predeploy contract. It implements
;; two ring buffers to create bounded beacon root lookup. The first ring
;; buffer is a timestamp % buflen -> timestamp mapping. This is used to ensure
;; timestamp argument actually matches the stored root and isn't different
;; dividend. The second ring buffer store the beacon root. It's also keyed by
;; timestamp % buflen and the shifted right by buflen so the two don't overlap.
;;
;; The ring buffers can be visualized as follows:
;;
;; buflen = 10
;; |--------------|--------------|
;; 0 10 20
;; timestamps beacon roots
;;
;; To get the corresponding beacon root for a specific timestamp, simply add
;; buflen to the timestamp's index in the first ring buffer. The sum will be
;; the storage slot in the second ring buffer where it is stored.


;; ----------------------------------------------------------------------------
;; 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]
push1 @submit ;; [submit_lbl, sysaddr == caller]
jumpi ;; []

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

;; Check if calldata is equal to 32 bytes.
push1 32 ;; [32]
calldatasize ;; [calldatasize, 32]
eq ;; [calldatasize == 32]

;; Jump to continue if length-check passed, otherwise revert.
push1 @loadtime ;; [loadtime_lbl, calldatasize == 32]
jumpi ;; []
%do_revert() ;; []

loadtime:
;; Load input timestamp.
push0 ;; [0]
calldataload ;; [input_timestamp]
dup1 ;; [input_timestamp, input_timestamp]

;; Verify input timestamp is non-zero.
iszero ;; [input_timestamp == 0, input_timestamp]
push1 @throw ;; [throw_lbl, input_timestamp == 0, input_timestamp]
jumpi ;; [input_timestamp]

;; Compute the timestamp index and load from storage.
push3 BUFLEN ;; [buflen, input_timestamp]
dup2 ;; [input_timestamp, buflen, input_timestamp]
mod ;; [time_index, input_timestamp]
swap1 ;; [input_timestamp, time_index]
dup2 ;; [time_index, input_timestamp, time_index]
sload ;; [stored_timestamp, input_timestamp, time_index]

;; Verify stored timestamp matches input timestamp. It's possible these
;; don't match if the slot has been overwritten by the ring buffer or if
;; the timestamp input wasn't a valid previous timestamp.
eq ;; [stored_timestamp == input_timestamp, time_index]
push1 @loadroot ;; [loadroot_lbl, input == timestamp, time_index]
jumpi ;; [time_index]
%do_revert() ;; []

loadroot:
;; Extend index to get root index.
push3 BUFLEN ;; [buflen, time_index]
add ;; [root_index]
sload ;; [root]

;; Write the retrieved root to memory so it can be returned.
push0 ;; [0, root]
mstore ;; []

;; Return the root.
push1 32 ;; [size]
push0 ;; [offset, size]
return ;; []

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

submit:
;; Calculate the index the timestamp should be stored at, e.g.
;; time_index = (time % buflen).
push3 BUFLEN ;; [buflen]
timestamp ;; [time, buflen]
mod ;; [time % buflen]

;; Write timestamp into storage slot at time_index.
timestamp ;; [time, time_index]
dup2 ;; [time_index, time, time_index]
sstore ;; [time_index]

;; Get root from calldata and write into root_index. No validation is
;; done on the input root. Becuase the routine is protected by a caller
;; check against sysaddr(), it's okay to assume the value is correctly
;; given.
push0 ;; [0, time_index]
calldataload ;; [root, time_index]
swap1 ;; [time_index, root]
push3 BUFLEN ;; [buflen, time_index, root]
add ;; [root_index, root]
sstore ;; []

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

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

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

function timestamp() view returns (bytes32) {
return bytes32(uint256(block.timestamp));
}

function timestamp_idx() view returns (bytes32) {
return bytes32(uint256(block.timestamp % buflen));
}

function root_idx() view returns (bytes32) {
return bytes32(uint256(block.timestamp % buflen + buflen));
}

contract ContractTest is Test {
address unit;

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

// testRead verifies the contract returns the expected beacon root.
function testRead() public {
// Store timestamp and root at expected indexes.
vm.store(unit, timestamp_idx(), timestamp());
vm.store(unit, root_idx(), root);

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

function testReadBadCalldataSize() public {
uint256 time = block.timestamp;

// Store timestamp and root at expected indexes.
vm.store(unit, timestamp_idx(), bytes32(time));
vm.store(unit, root_idx(), root);

// 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 testReadWrongTimestamp() public {
// Set reasonable timestamp.
vm.warp(1641070800);
uint256 time = block.timestamp;

// Store timestamp and root at expected indexes.
vm.store(unit, timestamp_idx(), bytes32(time));
vm.store(unit, root_idx(), root);

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

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

// Timestamp without any associated root.
(ret, data) = unit.call(bytes.concat(bytes32(time+1)));
assertFalse(ret);
assertEq(data, hex"");

// Timestamp 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 root.
vm.prank(sysaddr);
(bool ret, bytes memory data) = unit.call(bytes.concat(root));
assertTrue(ret);
assertEq(data, hex"");

// Verify timestamp.
bytes32 got = vm.load(unit, timestamp_idx());
assertEq(got, timestamp());

// Verify root.
got = vm.load(unit, root_idx());
assertEq(got, root);
}

// 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 {
for (uint256 i = 0; i < 10000; i += 1) {
bytes32 pbbr = bytes32(i*1337);

// Simulate pre-block call to set root.
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 beacon root associated
// with current timestamp.
(ret, data) = unit.call(bytes.concat(timestamp()));
assertTrue(ret);
assertEq(data, bytes.concat(pbbr));

// Skip forward 12 seconds.
skip(12);
}
}


// testHistoricalReads verifies that it is possible to read all previously
// saved values in the beacon root contract.
function testHistoricalReads() public {
uint256 start = block.timestamp;

// Saturate storage with fake roots.
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"");
skip(12);
}

// Attempt to read all values in same block context.
for (uint256 i = 0; i < buflen; i += 1) {
bytes32 time = bytes32(uint256(start+i*12));
(bool ret, bytes memory got) = unit.call(bytes.concat(time));
assertTrue(ret);
assertEq(got, bytes.concat(bytes32(i*1337)));
}
}
}
2 changes: 1 addition & 1 deletion test/Consolidation.t.sol.in
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ contract ConsolidationTest is Test {

function setUp() public {
vm.etch(addr, hex"@bytecode@");
vm.etch(fakeExpo, hex"@bytecode2@");
vm.etch(fakeExpo, hex"@bytecode_expo@");
}

// testInvalidRequest checks that common invalid requests are rejected.
Expand Down
2 changes: 1 addition & 1 deletion test/Withdrawal.t.sol.in
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ contract WithdrawalsTest is Test {

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

Expand Down

0 comments on commit 75e602a

Please sign in to comment.