-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add 4788 beacon contract (converted from etk)
- Loading branch information
1 parent
ee61e80
commit 75e602a
Showing
7 changed files
with
329 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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: |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 ;; [] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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))); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters