77 "context"
88 "encoding/binary"
99 "fmt"
10+ "maps"
1011 "math"
1112 "math/big"
1213 "math/rand/v2"
8384 hooks * saehookstest.Stub
8485 archival bool
8586 commitInterval uint64
87+ extraAlloc types.GenesisAlloc
8688 }
8789 sutOption = options.Option [sutConfig ]
8890)
@@ -106,6 +108,7 @@ func newSUT(tb testing.TB, opts ...sutOption) (context.Context, *SUT) {
106108
107109 wallet := saetest .NewUNSAFEWallet (tb , 1 , types .LatestSigner (config ))
108110 alloc := saetest .MaxAllocFor (wallet .Addresses ()... )
111+ maps .Copy (alloc , sutCfg .extraAlloc )
109112
110113 genOpts := []blockstest.GenesisOption {
111114 blockstest .WithTrieDBConfig (tdbConfig ),
@@ -157,6 +160,12 @@ func withHooks(h *saehookstest.Stub) sutOption {
157160 })
158161}
159162
163+ func withExtraAlloc (a types.GenesisAlloc ) sutOption {
164+ return options.Func [sutConfig ](func (c * sutConfig ) {
165+ c .extraAlloc = a
166+ })
167+ }
168+
160169func TestImmediateShutdownNonBlocking (t * testing.T ) {
161170 newSUT (t ) // calls [Executor.Close] in test cleanup
162171}
@@ -1054,3 +1063,94 @@ func TestArchivalStoresAll(t *testing.T) {
10541063 }
10551064 })
10561065}
1066+
1067+ // TestProcessBeaconBlockRoot verifies that block execution performs the
1068+ // EIP-4788 system call.
1069+ //
1070+ // The contract is deployed according to the EIP, which lands the contract at
1071+ // [params.BeaconRootsStorageAddress]. A later block then carries a parent
1072+ // beacon root, and we assert on the contract's observable behaviour by querying
1073+ // it for that block's timestamp.
1074+ func TestProcessBeaconBlockRoot (t * testing.T ) {
1075+ beaconRoot := common .HexToHash ("0xbeef" )
1076+
1077+ // This is the canonical EIP-4788 deployment transaction from
1078+ // https://eips.ethereum.org/EIPS/eip-4788#deployment.
1079+ deployer := common .HexToAddress ("0x0B799C86a49DEeb90402691F1041aa3AF2d3C875" )
1080+ deployTx := types .NewTx (& types.LegacyTx {
1081+ Nonce : 0 ,
1082+ GasPrice : big .NewInt (0xe8d4a51000 ),
1083+ Gas : 0x3d090 ,
1084+ Data : common .FromHex ("0x60618060095f395ff33373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500" ),
1085+ V : big .NewInt (0x1b ),
1086+ R : big .NewInt (0x539 ),
1087+ S : big .NewInt (0x1b9b6eb1f0 ),
1088+ })
1089+
1090+ { // test setup validation
1091+ require .Equal (t , common .HexToHash ("0xdf52c2d3bbe38820fff7b5eaab3db1b91f8e1412b56497d88388fb5d4ea1fde0" ), deployTx .Hash (), "deployment tx hash matches EIP" )
1092+ sender , err := types .Sender (types .LatestSigner (saetest .ChainConfig ()), deployTx )
1093+ require .NoError (t , err , "types.Sender([deployment tx])" )
1094+ require .Equal (t , deployer , sender , "types.Sender([deployment tx])" )
1095+ }
1096+
1097+ tests := []struct {
1098+ name string
1099+ beaconRoot * common.Hash
1100+ want []byte
1101+ wantErr testerr.Want
1102+ }{
1103+ {
1104+ name : "stores parent beacon root, queryable via beacon-roots contract" ,
1105+ beaconRoot : & beaconRoot ,
1106+ want : beaconRoot [:],
1107+ wantErr : nil ,
1108+ },
1109+ {
1110+ name : "nil parent beacon root: nothing stored, query reverts" ,
1111+ beaconRoot : nil ,
1112+ want : nil ,
1113+ wantErr : testerr .Is (vm .ErrExecutionReverted ),
1114+ },
1115+ }
1116+ for _ , tt := range tests {
1117+ t .Run (tt .name , func (t * testing.T ) {
1118+ // Fund only the keyless deployer EOA; the contract itself is deployed
1119+ // by the transaction, not pre-allocated.
1120+ ctx , sut := newSUT (t , withExtraAlloc (types.GenesisAlloc {
1121+ deployer : {Balance : big .NewInt (math .MaxInt64 )},
1122+ }))
1123+ e := sut .Executor
1124+
1125+ deployBlock := sut .chain .NewBlock (t , types.Transactions {deployTx })
1126+ require .NoError (t , e .Enqueue (ctx , deployBlock ), "Enqueue(deployment)" )
1127+
1128+ b := sut .chain .NewBlock (t , nil , blockstest .WithEthBlockOptions (
1129+ blockstest .ModifyHeader (func (h * types.Header ) {
1130+ h .Time = 1 // non-zero timestamp is required
1131+ h .ParentBeaconRoot = tt .beaconRoot
1132+ }),
1133+ ))
1134+ require .NoError (t , e .Enqueue (ctx , b ), "Enqueue(beacon)" )
1135+ require .NoErrorf (t , b .WaitUntilExecuted (ctx ), "%T.WaitUntilExecuted()" , b )
1136+
1137+ sdb , err := e .StateDB (b .PostExecutionStateRoot ())
1138+ require .NoErrorf (t , err , "%T.StateDB(%T.PostExecutionStateRoot())" , e , b )
1139+
1140+ header := b .Header ()
1141+ blockCtx := core .NewEVMBlockContext (header , nil /*chain; unused by get()*/ , & header .Coinbase )
1142+ evm := vm .NewEVM (blockCtx , vm.TxContext {}, sdb , saetest .ChainConfig (), vm.Config {})
1143+
1144+ got , _ , err := evm .StaticCall (
1145+ vm .AccountRef (common.Address {}),
1146+ params .BeaconRootsStorageAddress ,
1147+ uint256 .NewInt (header .Time ).PaddedBytes (32 ),
1148+ 1e6 ,
1149+ )
1150+ if diff := testerr .Diff (err , tt .wantErr ); diff != "" {
1151+ t .Errorf ("beacon-roots get() via StaticCall() %s" , diff )
1152+ }
1153+ assert .Equal (t , tt .want , got , "parent beacon root returned by beacon-roots contract" )
1154+ })
1155+ }
1156+ }
0 commit comments