Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ build: $(GEAS)
mkdir -p bytecode/factory
$(GEAS) -a -no-nl -o bytecode/factory/main.hex src/factory/main.eas

# 8141
mkdir -p bytecode/frames_expiry
$(GEAS) -a -no-nl -o bytecode/frames_expiry/main.hex src/frames_expiry/main.eas
$(GEAS) -a -no-nl -o bytecode/frames_expiry/ctor.hex src/frames_expiry/ctor.eas

# test helper
mkdir -p bytecode/fake_expo_test
$(GEAS) -a -no-nl -o bytecode/fake_expo_test/main.hex src/common/fake_expo_test.eas
Expand Down
1 change: 1 addition & 0 deletions bytecode/frames_expiry/ctor.hex
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
601a8060095f395ff360083614600a575f5ffd5b5f3560c01c4211601657005b5f5ffd
1 change: 1 addition & 0 deletions bytecode/frames_expiry/main.hex
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
60083614600a575f5ffd5b5f3560c01c4211601657005b5f5ffd
1 change: 1 addition & 0 deletions checksums.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ ea3eebcbb9549c4c158fbf92b77bccb250909095c824846ecbff6e4f4829249e bytecode/conso
b7ff005f1ad4e7adc56859f2fc3876b42e9216def18000c5373b69629adca7a6 bytecode/execution_hash/ctor.hex
1676499334d750e5cc9e6bd9bbaf8b78a901b675b1ea1c8c8dbcde2fbc7751b4 bytecode/execution_hash/main.hex
e48ada9a3056d5a2a52de5c078229849c3392e6a6c8e87ebb2c4b65bf1045e6d bytecode/factory/main.hex
fa7a30dec1a6fdf440b5a453f8c9f8d82a65f5a133b45b91477a464502b3f579 bytecode/frames_expiry/main.hex
cc660e9bde40daf93521f879909ecf7b3b207524356f8e87b888e68a44181f65 bytecode/withdrawals/ctor.hex
39c5522e72643757cc35d4cffc36848b2ab860d5ad38a18590b93513d550ba9f bytecode/withdrawals/main.hex
6 changes: 5 additions & 1 deletion scripts/addr.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ case $1 in
echo "searching for builder_exits deployment data "
bin/nick search --score=$score --initcode="0x$(cat bytecode/builder_exits/ctor.hex)" --prefix=0x8282 --suffix=0xeeee --gaslimit=$gaslimit
;;
frames_expiry)
echo "searching for frames_expiry deployment data "
bin/nick search --score=$score --initcode="0x$(cat bytecode/frames_expiry/ctor.hex)" --prefix=0x8141 --suffix=0xffff
;;
*)
echo "Invalid option. Usage: $0 {withdrawals|consolidations|exechash|beaconroot}"
echo "Invalid option. Usage: $0 {withdrawals|consolidations|exechash|beaconroot|builder_deposits|builder_exits|frames_expiry}"
;;
esac
10 changes: 10 additions & 0 deletions src/frames_expiry/ctor.eas
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
;; Copy and return code.
push len(code) ; [len]
dup1 ; [len, len]
push @code ; [codeOffset, len, len]
push 0 ; [dest, codeOffset, len, len]
codecopy ; [len]
push 0 ; [ptr, len]
return ; []

#bytes code: assemble("main.eas")
55 changes: 55 additions & 0 deletions src/frames_expiry/main.eas
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
;;; █████╗ ██╗██╗ ██╗ ██╗ █████╗ ███████╗███╗ ███╗
;;; ██╔══██╗███║██║ ██║███║ ██╔══██╗██╔════╝████╗ ████║
;;; ╚█████╔╝╚██║███████║╚██║ ███████║███████╗██╔████╔██║
;;; ██╔══██╗ ██║╚════██║ ██║ ██╔══██║╚════██║██║╚██╔╝██║
;;; ╚█████╔╝ ██║ ██║ ██║ ██║ ██║███████║██║ ╚═╝ ██║
;;; ╚════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝
;;;
;;; This is an implementation of EIP-8141's frames expiry contract. It
;;; is called by expiry verifier frames with an 8-byte big-endian expiry timestamp
;;; as calldata, and reverts unless block.timestamp <= expiry.

#pragma target "prague"

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

;;; EXPIRY_DATA_LENGTH is the size of the expiry timestamp input in bytes.
#define EXPIRY_DATA_LENGTH = 8

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

;; Verify input is exactly EXPIRY_DATA_LENGTH bytes long.
push EXPIRY_DATA_LENGTH ; [8]
calldatasize ; [calldatasize, 8]
eq ; [calldatasize == 8]
jumpi @check ; []

;; Input length is invalid, revert.
push 0 ; [0]
push 0 ; [0, 0]
revert ; []

check:
;; Load the expiry timestamp. The 8 input bytes occupy the high-order end
;; of the loaded word, so shift them down to get the numeric value.
push 0 ; [0]
calldataload ; [word]
push 256 - 8*EXPIRY_DATA_LENGTH ; [192, word]
shr ; [expiry]

;; Revert if the deadline has passed.
timestamp ; [timestamp, expiry]
gt ; [timestamp > expiry]
jumpi @expired ; []

;; Deadline has not passed, succeed with no return data.
stop

expired:
push 0 ; [0]
push 0 ; [0, 0]
revert ; []
66 changes: 66 additions & 0 deletions test/FramesExpiry.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "forge-std/Test.sol";

address constant addr = 0x0000000000000000000000000000000000008141;

contract FramesExpiryTest is Test {
function setUp() public {
vm.etch(addr, vm.parseBytes(vm.readFile("bytecode/frames_expiry/main.hex")));
vm.warp(1_700_000_000);
}

// verify calls the contract with an 8-byte big-endian expiry.
function verify(uint64 expiry) internal returns (bool) {
(bool ret, bytes memory data) = addr.call(abi.encodePacked(expiry));
assertEq(data, hex"", "unexpected return data");
return ret;
}

// testDeadline checks the timestamp boundary: expiry >= timestamp passes,
// expiry < timestamp reverts.
function testDeadline() public {
uint64 ts = uint64(block.timestamp);
assertTrue(verify(ts), "expiry == timestamp should pass");
assertTrue(verify(ts + 1), "future expiry should pass");
assertTrue(verify(type(uint64).max), "max expiry should pass");
assertFalse(verify(ts - 1), "past expiry should revert");
assertFalse(verify(0), "zero expiry should revert");
}

// testBadCalldataSize checks inputs that aren't exactly 8 bytes revert,
// even when their leading bytes encode a valid future expiry.
function testBadCalldataSize() public {
bytes memory future = abi.encodePacked(type(uint64).max);
bytes[5] memory inputs = [
bytes(hex""),
slice(future, 7),
bytes.concat(future, hex"00"),
bytes.concat(bytes32(type(uint256).max)),
bytes.concat(hex"deadbeef", future)
];
for (uint256 i = 0; i < inputs.length; i++) {
(bool ret, bytes memory data) = addr.call(inputs[i]);
assertFalse(ret);
assertEq(data, hex"");
}
}

// testDeploy checks the constructor installs the runtime code.
function testDeploy() public {
bytes memory initcode = vm.parseBytes(vm.readFile("bytecode/frames_expiry/ctor.hex"));
address deployed;
assembly {
deployed := create(0, add(initcode, 0x20), mload(initcode))
}
assertEq(deployed.code, addr.code);
}

function slice(bytes memory data, uint256 len) internal pure returns (bytes memory out) {
out = new bytes(len);
for (uint256 i = 0; i < len; i++) {
out[i] = data[i];
}
}
}
Loading