From f0dd2170c62121c7bdc1ba47389b6f1b790d9807 Mon Sep 17 00:00:00 2001 From: mralj Date: Fri, 27 Sep 2024 13:10:01 +0200 Subject: [PATCH] implements L1SLOAD contract --- core/vm/contracts_rollup.go | 105 +++++++++++++++++++++---------- core/vm/contracts_rollup_test.go | 32 ++++++++++ core/vm/contracts_test.go | 4 +- params/protocol_params_rollup.go | 7 +++ 4 files changed, 114 insertions(+), 34 deletions(-) create mode 100644 core/vm/contracts_rollup_test.go create mode 100644 params/protocol_params_rollup.go diff --git a/core/vm/contracts_rollup.go b/core/vm/contracts_rollup.go index d633e368905d..2b6140423ebc 100644 --- a/core/vm/contracts_rollup.go +++ b/core/vm/contracts_rollup.go @@ -4,27 +4,23 @@ package vm import ( + "context" "errors" "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" ) -type RollupPrecompiledContractsOverrides struct { - l1SLoadGetLatestL1Block func() *big.Int -} - -func GenerateRollupPrecompiledContractsOverrides(evm *EVM) RollupPrecompiledContractsOverrides { - return RollupPrecompiledContractsOverrides{ - l1SLoadGetLatestL1Block: getLatestL1BlockNumber(evm), - } -} - var rollupL1SloadAddress = common.BytesToAddress([]byte{0x10, 0x01}) var PrecompiledContractsRollupR0 = PrecompiledContracts{ - rollupL1SloadAddress: &l1SLoad{}, + rollupL1SloadAddress: &L1SLoad{}, +} + +type RollupPrecompileActivationConfig struct { + L1SLoad } func activeRollupPrecompiledContracts(rules params.Rules) PrecompiledContracts { @@ -36,40 +32,85 @@ func activeRollupPrecompiledContracts(rules params.Rules) PrecompiledContracts { } } -func (evm *EVM) activateRollupPrecompiledContracts() { - activeRollupPrecompiles := activeRollupPrecompiledContracts(evm.chainRules) - for k, v := range activeRollupPrecompiles { - evm.precompiles[k] = v - } - +func (pc *PrecompiledContracts) ActivateRollupPrecompiledContracts(config RollupPrecompileActivationConfig) { // NOTE: if L1SLoad was not activated via chain rules this is no-op - evm.precompiles.activateL1SLoad(evm.Config.L1RpcClient, evm.rollupPrecompileOverrides.l1SLoadGetLatestL1Block) + pc.activateL1SLoad(config.L1RpcClient, config.GetLatestL1BlockNumber) +} + +func (evm *EVM) activateRollupPrecompiledContracts() { + evm.precompiles.ActivateRollupPrecompiledContracts(RollupPrecompileActivationConfig{ + L1SLoad{L1RpcClient: evm.Config.L1RpcClient, GetLatestL1BlockNumber: evm.rollupPrecompileOverrides.l1SLoadGetLatestL1Block}, + }) } -type l1SLoad struct { - l1RpcClient L1Client - getLatestL1BlockNumber func() *big.Int +//INPUT SPECS: +//Byte range Name Description +//------------------------------------------------------------ +//[0: 19] (20 bytes) address The contract address +//[20: 51] (32 bytes) key1 The storage key +//... ... ... +//[k*32-12: k*32+19] (32 bytes) key_k The storage key + +type L1SLoad struct { + L1RpcClient L1RpcClient + GetLatestL1BlockNumber func() *big.Int } -func (c *l1SLoad) RequiredGas(input []byte) uint64 { return 0 } +func (c *L1SLoad) RequiredGas(input []byte) uint64 { + storageSlotsToLoad := len(input[common.AddressLength-1:]) / common.HashLength + storageSlotsToLoad = min(storageSlotsToLoad, params.L1SLoadMaxNumStorageSlots) -func (c *l1SLoad) Run(input []byte) ([]byte, error) { + return params.L1SLoadBaseGas + uint64(storageSlotsToLoad)*params.L1SLoadPerLoadGas +} + +func (c *L1SLoad) Run(input []byte) ([]byte, error) { if !c.isL1SLoadActive() { - return nil, errors.New("L1SLoad precompile not active") + log.Error("L1SLOAD called, but not activated", "client", c.L1RpcClient, "and latest block number function", c.GetLatestL1BlockNumber) + return nil, errors.New("L1SLOAD precompile not active") + } + + if len(input) < common.AddressLength+common.HashLength { + return nil, errors.New("L1SLOAD input too short") + } + + countOfStorageKeysToRead := (len(input) - common.AddressLength) / common.HashLength + thereIsAtLeast1StorageKeyToRead := countOfStorageKeysToRead > 0 + allStorageKeysAreExactly32Bytes := countOfStorageKeysToRead*common.HashLength == len(input)-common.AddressLength + + if inputIsValid := thereIsAtLeast1StorageKeyToRead && allStorageKeysAreExactly32Bytes; !inputIsValid { + return nil, errors.New("L1SLOAD input is malformed") + } + + contractAddress := common.BytesToAddress(input[:common.AddressLength]) + input = input[common.AddressLength-1:] + contractStorageKeys := make([]common.Hash, countOfStorageKeysToRead) + for k := 0; k < countOfStorageKeysToRead; k++ { + contractStorageKeys[k] = common.BytesToHash(input[k*common.HashLength : (k+1)*common.HashLength]) } - return nil, nil + // TODO: + // 1. Batch multiple storage slots + // 2. What about timeout strategy here? + res, err := c.L1RpcClient.StorageAt(context.Background(), contractAddress, contractStorageKeys[0], c.GetLatestL1BlockNumber()) + if err != nil { + return nil, err + } + + return res, nil } -func (c *l1SLoad) isL1SLoadActive() bool { - return c.getLatestL1BlockNumber != nil && c.l1RpcClient != nil +func (c *L1SLoad) isL1SLoadActive() bool { + return c.GetLatestL1BlockNumber != nil && c.L1RpcClient != nil } -func (pc *PrecompiledContracts) activateL1SLoad(l1RpcClient L1Client, getLatestL1BlockNumber func() *big.Int) { - if (*pc)[rollupL1SloadAddress] != nil { - (*pc)[rollupL1SloadAddress] = &l1SLoad{ - l1RpcClient: l1RpcClient, - getLatestL1BlockNumber: getLatestL1BlockNumber, +func (pc PrecompiledContracts) activateL1SLoad(l1RpcClient L1RpcClient, getLatestL1BlockNumber func() *big.Int) { + rulesSayContractShouldBeActive := pc[rollupL1SloadAddress] != nil + paramsNotNil := l1RpcClient != nil && getLatestL1BlockNumber != nil + + if shouldActivateL1SLoad := rulesSayContractShouldBeActive && paramsNotNil; shouldActivateL1SLoad { + pc[rollupL1SloadAddress] = &L1SLoad{ + L1RpcClient: l1RpcClient, + GetLatestL1BlockNumber: getLatestL1BlockNumber, } } } diff --git a/core/vm/contracts_rollup_test.go b/core/vm/contracts_rollup_test.go new file mode 100644 index 000000000000..cbca8188c9f6 --- /dev/null +++ b/core/vm/contracts_rollup_test.go @@ -0,0 +1,32 @@ +package vm + +import ( + "context" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" +) + +type MockL1RPCClient struct{} + +func (m MockL1RPCClient) StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) { + return common.Hex2Bytes("abab"), nil +} + +func TestPrecompiledL1SLOAD(t *testing.T) { + mockL1RPCClient := MockL1RPCClient{} + + allPrecompiles[rollupL1SloadAddress] = &L1SLoad{} + allPrecompiles.activateL1SLoad(mockL1RPCClient, func() *big.Int { return big1 }) + + l1SLoadTestcase := precompiledTest{ + Name: "L1SLOAD", + Input: "C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc22d2c7bb6fc06067df8b0223aec460d1ebb51febb9012bc2554141a4dca08e864", + Expected: "abab", + Gas: 4000, + NoBenchmark: true, + } + + testPrecompiled(rollupL1SloadAddress.Hex(), l1SLoadTestcase, t) +} diff --git a/core/vm/contracts_test.go b/core/vm/contracts_test.go index fff5c966f34f..23035e0eeec2 100644 --- a/core/vm/contracts_test.go +++ b/core/vm/contracts_test.go @@ -45,7 +45,7 @@ type precompiledFailureTest struct { // allPrecompiles does not map to the actual set of precompiles, as it also contains // repriced versions of precompiles at certain slots -var allPrecompiles = map[common.Address]PrecompiledContract{ +var allPrecompiles = PrecompiledContracts{ common.BytesToAddress([]byte{1}): &ecrecover{}, common.BytesToAddress([]byte{2}): &sha256hash{}, common.BytesToAddress([]byte{3}): &ripemd160hash{}, @@ -181,7 +181,7 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) { // Keep it as uint64, multiply 100 to get two digit float later mgasps := (100 * 1000 * gasUsed) / elapsed bench.ReportMetric(float64(mgasps)/100, "mgas/s") - //Check if it is correct + // Check if it is correct if err != nil { bench.Error(err) return diff --git a/params/protocol_params_rollup.go b/params/protocol_params_rollup.go new file mode 100644 index 000000000000..3ac56c6f63d2 --- /dev/null +++ b/params/protocol_params_rollup.go @@ -0,0 +1,7 @@ +package params + +const ( + L1SLoadBaseGas uint64 = 2000 // Base price for L1Sload + L1SLoadPerLoadGas uint64 = 2000 // Per-load price for loading one storage slot + L1SLoadMaxNumStorageSlots = 5 // Max number of storage slots requested in L1Sload precompile +)