Skip to content

Commit

Permalink
implements L1SLOAD contract
Browse files Browse the repository at this point in the history
  • Loading branch information
mralj committed Oct 4, 2024
1 parent 759dda7 commit f0dd217
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 34 deletions.
105 changes: 73 additions & 32 deletions core/vm/contracts_rollup.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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,
}
}
}
32 changes: 32 additions & 0 deletions core/vm/contracts_rollup_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
4 changes: 2 additions & 2 deletions core/vm/contracts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{},
Expand Down Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions params/protocol_params_rollup.go
Original file line number Diff line number Diff line change
@@ -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
)

0 comments on commit f0dd217

Please sign in to comment.