Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[P2] Implements RIP-7728 #4

Merged
merged 6 commits into from
Oct 7, 2024
Merged
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
62 changes: 56 additions & 6 deletions core/vm/contracts_rollup.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
package vm

import (
"context"
"errors"
"math/big"
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
)

Expand Down Expand Up @@ -41,31 +44,78 @@ func (evm *EVM) activateRollupPrecompiledContracts() {
})
}

//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)

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 inputIsInvalid := !(thereIsAtLeast1StorageKeyToRead && allStorageKeysAreExactly32Bytes); inputIsInvalid {
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])
}

var ctx context.Context
if params.L1SLoadRPCTimeoutInSec > 0 {
c, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(params.L1SLoadRPCTimeoutInSec))
ctx = c
defer cancel()
} else {
ctx = context.Background()
}

res, err := c.L1RpcClient.StoragesAt(ctx, contractAddress, contractStorageKeys, c.GetLatestL1BlockNumber())
if err != nil {
return nil, err
}

return nil, nil
return res, nil
}

func (c *L1SLoad) isL1SLoadActive() bool {
return c.GetLatestL1BlockNumber != nil && c.L1RpcClient != nil
}

func (pc *PrecompiledContracts) activateL1SLoad(l1RpcClient L1RpcClient, getLatestL1BlockNumber func() *big.Int) {
rulesSayContractShouldBeActive := (*pc)[rollupL1SloadAddress] != nil
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{
pc[rollupL1SloadAddress] = &L1SLoad{
L1RpcClient: l1RpcClient,
GetLatestL1BlockNumber: getLatestL1BlockNumber,
}
Expand Down
17 changes: 1 addition & 16 deletions core/vm/contracts_rollup_overrides.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@

package vm

import (
"math/big"
)
import "math/big"

type RollupPrecompiledContractsOverrides struct {
l1SLoadGetLatestL1Block func() *big.Int
Expand All @@ -25,16 +23,3 @@ func getLatestL1BlockNumber(evm *EVM) func() *big.Int {
return evm.Context.BlockNumber
}
}

// [OVERRIDE] getLatestL1BlockNumber
// Each rollup should override this function so that it returns
// correct latest L1 block number
//
// EXAMPLE 2
// func getLatestL1BlockNumber(evm *EVM) func() *big.Int {
// return func() *big.Int {
// addressOfL1BlockContract := common.Address{}
// slotInContractRepresentingL1BlockNumber := common.Hash{}
// return evm.StateDB.GetState(addressOfL1BlockContract, slotInContractRepresentingL1BlockNumber).Big()
// }
// }
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) StoragesAt(ctx context.Context, account common.Address, keys []common.Hash, blockNumber *big.Int) ([]byte, error) {
// testcase is in format "abab", this makes output lenght 2 bytes
const mockedRespValueSize = 2
mockResp := make([]byte, mockedRespValueSize*len(keys))
for i := range keys {
copy(mockResp[mockedRespValueSize*i:], common.Hex2Bytes("abab"))
}

return mockResp, nil
}

func TestPrecompiledL1SLOAD(t *testing.T) {
mockL1RPCClient := MockL1RPCClient{}

allPrecompiles[rollupL1SloadAddress] = &L1SLoad{}
allPrecompiles.activateL1SLoad(mockL1RPCClient, func() *big.Int { return big1 })

testJson("l1sload", rollupL1SloadAddress.Hex(), t)
testJsonFail("l1sload", rollupL1SloadAddress.Hex(), 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
2 changes: 1 addition & 1 deletion core/vm/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ type Config struct {

// [rollup-geth]
type L1RpcClient interface {
StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error)
StoragesAt(ctx context.Context, account common.Address, keys []common.Hash, blockNumber *big.Int) ([]byte, error)
}

// ScopeContext contains the things that are per-call, such as stack and memory,
Expand Down
24 changes: 24 additions & 0 deletions core/vm/testdata/precompiles/fail-l1sload.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[
{
"Name": "L1SLOAD FAIL: input contains only address",
"Input": "a83114A443dA1CecEFC50368531cACE9F37fCCcb",
"ExpectedError": "L1SLOAD input too short",
"Gas": 4000,
"NoBenchmark": true
},
{
"Name": "L1SLOAD FAIL: input key not 32 bytes",
"Input": "a83114A443dA1CecEFC50368531cACE9F37fCCcb112d016b65e9c617ad9ab60604f772a3620177bada4cdc773d9b6a982d3c2a7122",
"ExpectedError": "L1SLOAD input is malformed",
"Gas": 4000,
"NoBenchmark": true
},
{
"Name": "L1SLOAD FAIL: input too long",
"Input": "C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc22d2c7bb6fc06067df8b0223aec460d1ebb51febb9012bc2554141a4dca08e8640112d016b65e9c617ad9ab60604f772a3620177bada4cdc773d9b6a982d3c2a7a112d016b65e9c617ad9ab60604f772a3620177bada4cdc773d9b6a982d3c2a7a112d016b65e9c617ad9ab60604f772a3620177bada4cdc773d9b6a982d3c2a7a112d016b65e9c617ad9ab60604f772a3620177bada4cdc773d9b6a982d3c2a7aa83114A443dA1CecEFC50368531cACE9F37fCCcb112d016b65e9c617ad9ab60604f772a3620177bada4cdc773d9b6a982d3c2a7122",
"ExpectedError": "L1SLOAD input is malformed",
"Gas": 4000,
"NoBenchmark": true
}

]
24 changes: 24 additions & 0 deletions core/vm/testdata/precompiles/l1sload.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[
{
"Name": "L1SLOAD: 1 key",
"Input": "C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc22d2c7bb6fc06067df8b0223aec460d1ebb51febb9012bc2554141a4dca08e864",
"Expected": "abab",
"Gas": 4000,
"NoBenchmark": true
},
{
"Name": "L1SLOAD: 2 keys",
"Input": "C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc22d2c7bb6fc06067df8b0223aec460d1ebb51febb9012bc2554141a4dca08e8640112d016b65e9c617ad9ab60604f772a3620177bada4cdc773d9b6a982d3c2a7a",
"Expected": "abababab",
"Gas": 6000,
"NoBenchmark": true
},

{
"Name": "L1SLOAD: 5 keys",
"Input": "C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc22d2c7bb6fc06067df8b0223aec460d1ebb51febb9012bc2554141a4dca08e8640112d016b65e9c617ad9ab60604f772a3620177bada4cdc773d9b6a982d3c2a7a112d016b65e9c617ad9ab60604f772a3620177bada4cdc773d9b6a982d3c2a7a112d016b65e9c617ad9ab60604f772a3620177bada4cdc773d9b6a982d3c2a7a112d016b65e9c617ad9ab60604f772a3620177bada4cdc773d9b6a982d3c2a7a",
"Expected": "abababababababababab",
"Gas": 12000,
"NoBenchmark": true
}
]
7 changes: 7 additions & 0 deletions eth/tracers/api_rollup_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package tracers

import "github.com/ethereum/go-ethereum/core/vm"

func (b *testBackend) GetL1RpcClient() vm.L1RpcClient {
return nil
}
38 changes: 38 additions & 0 deletions ethclient/ethclient_rollup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package ethclient

import (
"context"
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rpc"
)

// StoragesAt returns the values of keys in the contract storage of the given account.
// The block number can be nil, in which case the value is taken from the latest known block.
func (ec *Client) StoragesAt(ctx context.Context, account common.Address, keys []common.Hash, blockNumber *big.Int) ([]byte, error) {
results := make([]hexutil.Bytes, len(keys))
reqs := make([]rpc.BatchElem, len(keys))

for i := range reqs {
reqs[i] = rpc.BatchElem{
Method: "eth_getStorageAt",
Args: []interface{}{account, keys[i], toBlockNumArg(blockNumber)},
Result: &results[i],
}
}
if err := ec.c.BatchCallContext(ctx, reqs); err != nil {
return nil, err
}

output := make([]byte, common.HashLength*len(keys))
for i := range reqs {
if reqs[i].Error != nil {
return nil, reqs[i].Error
}
copy(output[i*common.HashLength:], results[i])
}

return output, nil
}
7 changes: 7 additions & 0 deletions internal/ethapi/api_rollup_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package ethapi

import "github.com/ethereum/go-ethereum/core/vm"

func (b *testBackend) GetL1RpcClient() vm.L1RpcClient {
return nil
}
7 changes: 7 additions & 0 deletions internal/ethapi/transaction_args_rollup_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package ethapi

import "github.com/ethereum/go-ethereum/core/vm"

func (b *backendMock) GetL1RpcClient() vm.L1RpcClient {
return nil
}
8 changes: 8 additions & 0 deletions params/protocol_params_rollup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
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
L1SLoadRPCTimeoutInSec = 3
)