From d424177fc0c351c1489d72bf9b69cde1d6ffe82b Mon Sep 17 00:00:00 2001 From: ramtinms Date: Thu, 4 Jan 2024 13:40:27 -0800 Subject: [PATCH 01/14] precompile setup --- fvm/evm/emulator/config.go | 18 +++++++++ fvm/evm/emulator/emulator.go | 32 +++++++++++++++ fvm/evm/emulator/emulator_test.go | 66 ++++++++++++++++++++++++++++++- fvm/evm/types/emulator.go | 6 +++ 4 files changed, 121 insertions(+), 1 deletion(-) diff --git a/fvm/evm/emulator/config.go b/fvm/evm/emulator/config.go index b15ede521e9..1c7aefc4b5b 100644 --- a/fvm/evm/emulator/config.go +++ b/fvm/evm/emulator/config.go @@ -5,8 +5,10 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" + gethCommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/vm" + gethVM "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" ) @@ -30,6 +32,15 @@ type Config struct { TxContext *vm.TxContext // base unit of gas for direct calls DirectCallBaseGasUsage uint64 + // a set of extra precompiles to be injected + ExtraPrecompiles map[gethCommon.Address]gethVM.PrecompiledContract +} + +func (c *Config) ChainRules() params.Rules { + return c.ChainConfig.Rules( + c.BlockContext.BlockNumber, + c.BlockContext.Random != nil, + c.BlockContext.Time) } // DefaultChainConfig is the default chain config which @@ -176,3 +187,10 @@ func WithDirectCallBaseGasUsage(gas uint64) Option { return c } } + +func WithExtraPrecompiles(pc map[common.Address]vm.PrecompiledContract) Option { + return func(c *Config) *Config { + c.ExtraPrecompiles = pc + return c + } +} diff --git a/fvm/evm/emulator/emulator.go b/fvm/evm/emulator/emulator.go index 87858f873f8..ce68f322db2 100644 --- a/fvm/evm/emulator/emulator.go +++ b/fvm/evm/emulator/emulator.go @@ -1,6 +1,7 @@ package emulator import ( + "fmt" "math/big" gethCommon "github.com/ethereum/go-ethereum/common" @@ -39,6 +40,7 @@ func newConfig(ctx types.BlockContext) *Config { WithBlockNumber(new(big.Int).SetUint64(ctx.BlockNumber)), WithCoinbase(ctx.GasFeeCollector.ToCommon()), WithDirectCallBaseGasUsage(ctx.DirectCallBaseGasUsage), + WithExtraPrecompiles(ctx.ExtraPrecompiles), ) } @@ -53,6 +55,10 @@ func (em *Emulator) NewReadOnlyBlockView(ctx types.BlockContext) (types.ReadOnly // NewBlockView constructs a new block view (mutable) func (em *Emulator) NewBlockView(ctx types.BlockContext) (types.BlockView, error) { cfg := newConfig(ctx) + err := SetupPrecompile(cfg) + if err != nil { + return nil, err + } return &BlockView{ config: cfg, rootAddr: em.rootAddr, @@ -279,3 +285,29 @@ func (proc *procedure) run(msg *gethCore.Message, txType uint8) (*types.Result, } return &res, err } + +func SetupPrecompile(cfg *Config) error { + rules := cfg.ChainRules() + var precompiles map[gethCommon.Address]gethVM.PrecompiledContract + switch { + case rules.IsCancun: + precompiles = gethVM.PrecompiledContractsCancun + case rules.IsBerlin: + precompiles = gethVM.PrecompiledContractsBerlin + case rules.IsIstanbul: + precompiles = gethVM.PrecompiledContractsIstanbul + case rules.IsByzantium: + precompiles = gethVM.PrecompiledContractsByzantium + default: + precompiles = gethVM.PrecompiledContractsHomestead + } + + for k, v := range cfg.ExtraPrecompiles { + _, alreadyExist := precompiles[k] + if alreadyExist { + return fmt.Errorf("can not add a precompile, address already in use") + } + precompiles[k] = v + } + return nil +} diff --git a/fvm/evm/emulator/emulator_test.go b/fvm/evm/emulator/emulator_test.go index 3cca27b0906..6f14c683fd6 100644 --- a/fvm/evm/emulator/emulator_test.go +++ b/fvm/evm/emulator/emulator_test.go @@ -335,7 +335,6 @@ func TestStorageNoSideEffect(t *testing.T) { var err error em := emulator.NewEmulator(backend, flowEVMRoot) testAccount := types.NewAddressFromString("test") - amount := big.NewInt(10) RunWithNewBlockView(t, em, func(blk types.BlockView) { _, err = blk.DirectCall(types.NewDepositCall(testAccount, amount)) @@ -351,3 +350,68 @@ func TestStorageNoSideEffect(t *testing.T) { }) }) } + +func TestCallingExtraPrecompiles(t *testing.T) { + testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestFlowEVMRootAddress(t, backend, func(flowEVMRoot flow.Address) { + RunWithNewEmulator(t, backend, flowEVMRoot, func(em *emulator.Emulator) { + + testAccount := types.NewAddressFromString("test") + amount := big.NewInt(10_000_000) + RunWithNewBlockView(t, em, func(blk types.BlockView) { + _, err := blk.DirectCall(types.NewDepositCall(testAccount, amount)) + require.NoError(t, err) + }) + + input := []byte{1, 2} + output := []byte{3, 4} + pc := &MockedPrecompile{ + RequiredGasFunc: func(input []byte) uint64 { + return uint64(10) + }, + RunFunc: func(inp []byte) ([]byte, error) { + require.Equal(t, input, inp) + return output, nil + }, + } + addr := gethCommon.BytesToAddress([]byte{128, 128}) + ctx := types.NewDefaultBlockContext(blockNumber.Uint64()) + ctx.ExtraPrecompiles[addr] = pc + + blk, err := em.NewBlockView(ctx) + require.NoError(t, err) + + res, err := blk.DirectCall( + types.NewContractCall( + testAccount, + types.NewAddress(addr), + input, + 1_000_000, + big.NewInt(0), // this should be zero because the contract doesn't have receiver + ), + ) + require.NoError(t, err) + require.Equal(t, output, res.ReturnedValue) + }) + }) + }) +} + +type MockedPrecompile struct { + RequiredGasFunc func(input []byte) uint64 + RunFunc func(input []byte) ([]byte, error) +} + +func (mp *MockedPrecompile) RequiredGas(input []byte) uint64 { + if mp.RequiredGasFunc == nil { + panic("RequiredGas not set for the mocked precompile") + } + return mp.RequiredGasFunc(input) +} + +func (mp *MockedPrecompile) Run(input []byte) ([]byte, error) { + if mp.RunFunc == nil { + panic("Run not set for the mocked precompile") + } + return mp.RunFunc(input) +} diff --git a/fvm/evm/types/emulator.go b/fvm/evm/types/emulator.go index 73eef076a5b..1310c94759d 100644 --- a/fvm/evm/types/emulator.go +++ b/fvm/evm/types/emulator.go @@ -3,7 +3,9 @@ package types import ( "math/big" + gethCommon "github.com/ethereum/go-ethereum/common" gethTypes "github.com/ethereum/go-ethereum/core/types" + gethVM "github.com/ethereum/go-ethereum/core/vm" ) var ( @@ -20,6 +22,9 @@ type BlockContext struct { DirectCallBaseGasUsage uint64 DirectCallGasPrice uint64 GasFeeCollector Address + + // a set of extra precompiles to be injected + ExtraPrecompiles map[gethCommon.Address]gethVM.PrecompiledContract } // NewDefaultBlockContext returns a new default block context @@ -28,6 +33,7 @@ func NewDefaultBlockContext(BlockNumber uint64) BlockContext { BlockNumber: BlockNumber, DirectCallBaseGasUsage: DefaultDirectCallBaseGasUsage, DirectCallGasPrice: DefaultDirectCallGasPrice, + ExtraPrecompiles: make(map[gethCommon.Address]gethVM.PrecompiledContract), } } From 0ecaad4c787c5418af4d60513e14fedb3f985141 Mon Sep 17 00:00:00 2001 From: ramtinms Date: Wed, 10 Jan 2024 13:01:26 -0800 Subject: [PATCH 02/14] add precompile package --- fvm/evm/handler/handler.go | 1 + fvm/evm/precompiles/arch.go | 75 +++++++++++++++++++++++++++++++ fvm/evm/precompiles/precompile.go | 58 ++++++++++++++++++++++++ 3 files changed, 134 insertions(+) create mode 100644 fvm/evm/precompiles/arch.go create mode 100644 fvm/evm/precompiles/precompile.go diff --git a/fvm/evm/handler/handler.go b/fvm/evm/handler/handler.go index acbfc1c173e..11c769be490 100644 --- a/fvm/evm/handler/handler.go +++ b/fvm/evm/handler/handler.go @@ -148,6 +148,7 @@ func (h *ContractHandler) getBlockContext() types.BlockContext { return types.BlockContext{ BlockNumber: bp.Height, DirectCallBaseGasUsage: types.DefaultDirectCallBaseGasUsage, + // ExtraPrecompiles: precompiles.GetArchContract(), } } diff --git a/fvm/evm/precompiles/arch.go b/fvm/evm/precompiles/arch.go new file mode 100644 index 00000000000..81445cb2c7d --- /dev/null +++ b/fvm/evm/precompiles/arch.go @@ -0,0 +1,75 @@ +package precompiles + +import ( + "encoding/binary" + "fmt" + + gethVM "github.com/ethereum/go-ethereum/core/vm" +) + +var ( + FlowBlockHeightMethodID = [4]byte{1, 2, 3, 4} // TODO fill in with proper value + FlowBlockHeightFixedGas = uint64(0) // TODO update me with a proper value + + VerifyProofMethodID = [4]byte{3, 4, 5, 6} // TODO fill in with proper value + VerifyProofFixedGas = uint64(0) // TODO update me with a proper value + + true32Byte = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} + false32Byte = make([]byte, 32) +) + +type flowBlockHeightCallable struct { + flowBlockHeight uint64 +} + +func (c *flowBlockHeightCallable) MethodID() MethodID { + return FlowBlockHeightMethodID +} + +func (c *flowBlockHeightCallable) ComputeGas(input []byte) uint64 { + return FlowBlockHeightFixedGas +} + +func (c *flowBlockHeightCallable) Run(input []byte) ([]byte, error) { + if len(input) > 0 { + return nil, fmt.Errorf("unexpected input is provided") + } + // TODO maybe replace this with what abi encode does + encoded := make([]byte, 8) + binary.BigEndian.PutUint64(encoded, c.flowBlockHeight) + return encoded, nil +} + +type verifyProofCallable struct { + verifyProof func([]byte) (bool, error) +} + +func (c *verifyProofCallable) MethodID() MethodID { + return VerifyProofMethodID +} + +func (c *verifyProofCallable) ComputeGas(input []byte) uint64 { + return VerifyProofFixedGas +} + +func (c *verifyProofCallable) Run(input []byte) ([]byte, error) { + res, err := c.verifyProof(input) + if err != nil { + return nil, err + } + if res { + return true32Byte, err + } + return false32Byte, err +} + +func GetArchContract(flowBlockHeight uint64, verifyProof func([]byte) (bool, error)) gethVM.PrecompiledContract { + fbh := &flowBlockHeightCallable{flowBlockHeight} + vc := &verifyProofCallable{verifyProof} + return GetPrecompileContract( + map[MethodID]Callable{ + fbh.MethodID(): fbh, + vc.MethodID(): vc, + }, + ) +} diff --git a/fvm/evm/precompiles/precompile.go b/fvm/evm/precompiles/precompile.go new file mode 100644 index 00000000000..4189b5ac7b6 --- /dev/null +++ b/fvm/evm/precompiles/precompile.go @@ -0,0 +1,58 @@ +package precompiles + +import ( + "fmt" + + gethVM "github.com/ethereum/go-ethereum/core/vm" +) + +// This is derived as the first 4 bytes of the Keccak hash of the ASCII form of the signature of the method +type MethodID [4]byte + +type Callable interface { + MethodID() MethodID + + ComputeGas(input []byte) uint64 + + Run(input []byte) ([]byte, error) +} + +func GetPrecompileContract(callables map[MethodID]Callable) gethVM.PrecompiledContract { + return &Precompile{callables: callables} +} + +type Precompile struct { + callables map[MethodID]Callable +} + +// RequiredPrice calculates the contract gas use +func (p *Precompile) RequiredGas(input []byte) uint64 { + if len(input) < 4 { + return 0 + } + mID, data := splitMethodID(input) + callable, found := p.callables[mID] + if !found { + return 0 + } + return callable.ComputeGas(data) +} + +// Run runs the precompiled contract +func (p *Precompile) Run(input []byte) ([]byte, error) { + if len(input) < 4 { + return nil, fmt.Errorf("invalid method") // TODO return the right error based on geth + } + mID, data := splitMethodID(input) + callable, found := p.callables[mID] + if !found { + return nil, fmt.Errorf("invalid method") // TODO return the right error based on geth + } + return callable.Run(data) +} + +func splitMethodID(input []byte) (MethodID, []byte) { + var methodID MethodID + copy(methodID[:], input[0:4]) + return methodID, input[4:] +} From f0c44f546b61616da6764ec2ee87f13f4e5bd6db Mon Sep 17 00:00:00 2001 From: ramtinms Date: Wed, 10 Jan 2024 15:32:38 -0800 Subject: [PATCH 03/14] expose arch handler --- fvm/evm/emulator/config.go | 8 ++++-- fvm/evm/emulator/emulator_test.go | 18 ++++++++++--- fvm/evm/handler/addressAllocator.go | 34 +++++++++++++++++++----- fvm/evm/handler/addressAllocator_test.go | 8 +++--- fvm/evm/handler/handler.go | 24 +++++++++++++++-- fvm/evm/handler/handler_test.go | 20 +++++++------- fvm/evm/precompiles/arch.go | 25 +++++++++++------ fvm/evm/precompiles/precompile.go | 17 +++++++++--- fvm/evm/types/emulator.go | 10 ++++--- fvm/evm/types/handler.go | 14 ++++++++-- 10 files changed, 135 insertions(+), 43 deletions(-) diff --git a/fvm/evm/emulator/config.go b/fvm/evm/emulator/config.go index 1c7aefc4b5b..16b4ed607f4 100644 --- a/fvm/evm/emulator/config.go +++ b/fvm/evm/emulator/config.go @@ -11,6 +11,7 @@ import ( gethVM "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" + "github.com/onflow/flow-go/fvm/evm/types" ) var ( @@ -93,6 +94,7 @@ func defaultConfig() *Config { return common.BytesToHash(crypto.Keccak256([]byte(new(big.Int).SetUint64(n).String()))) }, }, + ExtraPrecompiles: make(map[gethCommon.Address]gethVM.PrecompiledContract), } } @@ -188,9 +190,11 @@ func WithDirectCallBaseGasUsage(gas uint64) Option { } } -func WithExtraPrecompiles(pc map[common.Address]vm.PrecompiledContract) Option { +func WithExtraPrecompiles(precompiles []types.Precompile) Option { return func(c *Config) *Config { - c.ExtraPrecompiles = pc + for _, pc := range precompiles { + c.ExtraPrecompiles[pc.Address().ToCommon()] = pc + } return c } } diff --git a/fvm/evm/emulator/emulator_test.go b/fvm/evm/emulator/emulator_test.go index 6f14c683fd6..a7c5768cca0 100644 --- a/fvm/evm/emulator/emulator_test.go +++ b/fvm/evm/emulator/emulator_test.go @@ -365,7 +365,11 @@ func TestCallingExtraPrecompiles(t *testing.T) { input := []byte{1, 2} output := []byte{3, 4} + addr := testutils.RandomAddress(t) pc := &MockedPrecompile{ + AddressFunc: func() types.Address { + return addr + }, RequiredGasFunc: func(input []byte) uint64 { return uint64(10) }, @@ -374,9 +378,9 @@ func TestCallingExtraPrecompiles(t *testing.T) { return output, nil }, } - addr := gethCommon.BytesToAddress([]byte{128, 128}) + ctx := types.NewDefaultBlockContext(blockNumber.Uint64()) - ctx.ExtraPrecompiles[addr] = pc + ctx.ExtraPrecompiles = []types.Precompile{pc} blk, err := em.NewBlockView(ctx) require.NoError(t, err) @@ -384,7 +388,7 @@ func TestCallingExtraPrecompiles(t *testing.T) { res, err := blk.DirectCall( types.NewContractCall( testAccount, - types.NewAddress(addr), + types.NewAddress(addr.ToCommon()), input, 1_000_000, big.NewInt(0), // this should be zero because the contract doesn't have receiver @@ -398,10 +402,18 @@ func TestCallingExtraPrecompiles(t *testing.T) { } type MockedPrecompile struct { + AddressFunc func() types.Address RequiredGasFunc func(input []byte) uint64 RunFunc func(input []byte) ([]byte, error) } +func (mp *MockedPrecompile) Address() types.Address { + if mp.AddressFunc == nil { + panic("Address not set for the mocked precompile") + } + return mp.AddressFunc() +} + func (mp *MockedPrecompile) RequiredGas(input []byte) uint64 { if mp.RequiredGasFunc == nil { panic("RequiredGas not set for the mocked precompile") diff --git a/fvm/evm/handler/addressAllocator.go b/fvm/evm/handler/addressAllocator.go index 28e476d9427..29eb599d830 100644 --- a/fvm/evm/handler/addressAllocator.go +++ b/fvm/evm/handler/addressAllocator.go @@ -11,6 +11,12 @@ import ( const ledgerAddressAllocatorKey = "AddressAllocator" +var ( + FlowEVMAddressSpacePrefix = []byte{0, 0, 0, 0, 0, 0, 0, 1} + FlowEVMPrecompileAddressPrefix = []byte{0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1} + FlowEVMCOAAddressPrefix = []byte{0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2} +) + type AddressAllocator struct { led atree.Ledger flexAddress flow.Address @@ -26,8 +32,8 @@ func NewAddressAllocator(led atree.Ledger, flexAddress flow.Address) (*AddressAl }, nil } -// AllocateAddress allocates an address -func (aa *AddressAllocator) AllocateAddress() (types.Address, error) { +// AllocateCOAAddress allocates an address for COA +func (aa *AddressAllocator) AllocateCOAAddress() (types.Address, error) { data, err := aa.led.GetValue(aa.flexAddress[:], []byte(ledgerAddressAllocatorKey)) if err != nil { return types.Address{}, err @@ -38,10 +44,7 @@ func (aa *AddressAllocator) AllocateAddress() (types.Address, error) { uuid = binary.BigEndian.Uint64(data) } - target := types.Address{} - // first 12 bytes would be zero - // the next 8 bytes would be an increment of the UUID index - binary.BigEndian.PutUint64(target[12:], uuid) + target := makeCOAAddress(uuid) // store new uuid newData := make([]byte, 8) @@ -53,3 +56,22 @@ func (aa *AddressAllocator) AllocateAddress() (types.Address, error) { return target, nil } + +func makeCOAAddress(index uint64) types.Address { + var addr types.Address + copy(addr[:types.AddressLength], FlowEVMCOAAddressPrefix) + binary.BigEndian.PutUint64(addr[types.AddressLength-8:], index) + return addr +} + +func (aa *AddressAllocator) AllocatePrecompileAddress(index uint64) types.Address { + target := makePrecompileAddress(index) + return target +} + +func makePrecompileAddress(index uint64) types.Address { + var addr types.Address + copy(addr[:types.AddressLength], FlowEVMPrecompileAddressPrefix) + binary.BigEndian.PutUint64(addr[types.AddressLength-8:], index) + return addr +} diff --git a/fvm/evm/handler/addressAllocator_test.go b/fvm/evm/handler/addressAllocator_test.go index ab8eb0de2b4..dbbf6e0c601 100644 --- a/fvm/evm/handler/addressAllocator_test.go +++ b/fvm/evm/handler/addressAllocator_test.go @@ -20,15 +20,15 @@ func TestAddressAllocator(t *testing.T) { require.NoError(t, err) // test default value fall back - adr, err := aa.AllocateAddress() + adr, err := aa.AllocateCOAAddress() require.NoError(t, err) - expectedAddress := types.NewAddress(gethCommon.HexToAddress("0x00000000000000000001")) + expectedAddress := types.NewAddress(gethCommon.HexToAddress("0x00000001000200000001")) require.Equal(t, expectedAddress, adr) // continous allocation logic - adr, err = aa.AllocateAddress() + adr, err = aa.AllocateCOAAddress() require.NoError(t, err) - expectedAddress = types.NewAddress(gethCommon.HexToAddress("0x00000000000000000002")) + expectedAddress = types.NewAddress(gethCommon.HexToAddress("0x00000001000200000002")) require.Equal(t, expectedAddress, adr) }) diff --git a/fvm/evm/handler/handler.go b/fvm/evm/handler/handler.go index 11c769be490..1a643dddddc 100644 --- a/fvm/evm/handler/handler.go +++ b/fvm/evm/handler/handler.go @@ -9,6 +9,7 @@ import ( "github.com/onflow/flow-go/fvm/environment" "github.com/onflow/flow-go/fvm/errors" + "github.com/onflow/flow-go/fvm/evm/precompiles" "github.com/onflow/flow-go/fvm/evm/types" ) @@ -28,6 +29,7 @@ type ContractHandler struct { addressAllocator types.AddressAllocator backend types.Backend emulator types.Emulator + precompiles []types.Precompile } func (h *ContractHandler) FlowTokenAddress() common.Address { @@ -42,6 +44,7 @@ func NewContractHandler( addressAllocator types.AddressAllocator, backend types.Backend, emulator types.Emulator, + archProvider types.CadenceArchProvider, ) *ContractHandler { return &ContractHandler{ flowTokenAddress: flowTokenAddress, @@ -49,12 +52,29 @@ func NewContractHandler( addressAllocator: addressAllocator, backend: backend, emulator: emulator, + precompiles: getPrecompiles(addressAllocator, archProvider), } } +func getPrecompiles( + addressAllocator types.AddressAllocator, + archProvider types.CadenceArchProvider, +) []types.Precompile { + if archProvider == nil || addressAllocator == nil { + return nil + } + archAddress := addressAllocator.AllocatePrecompileAddress(1) + archContract := precompiles.ArchContract( + archAddress, + archProvider.FlowBlockHeight, + archProvider.VerifyAccountProof, + ) + return []types.Precompile{archContract} +} + // AllocateAddress allocates an address to be used by the bridged accounts func (h *ContractHandler) AllocateAddress() types.Address { - target, err := h.addressAllocator.AllocateAddress() + target, err := h.addressAllocator.AllocateCOAAddress() handleError(err) return target } @@ -148,7 +168,7 @@ func (h *ContractHandler) getBlockContext() types.BlockContext { return types.BlockContext{ BlockNumber: bp.Height, DirectCallBaseGasUsage: types.DefaultDirectCallBaseGasUsage, - // ExtraPrecompiles: precompiles.GetArchContract(), + ExtraPrecompiles: h.precompiles, } } diff --git a/fvm/evm/handler/handler_test.go b/fvm/evm/handler/handler_test.go index 0acf3d3ceff..0b37e501824 100644 --- a/fvm/evm/handler/handler_test.go +++ b/fvm/evm/handler/handler_test.go @@ -65,7 +65,7 @@ func TestHandler_TransactionRun(t *testing.T) { return result, nil }, } - handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, em) + handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, em, nil) coinbase := types.NewAddress(gethCommon.Address{}) @@ -143,7 +143,7 @@ func TestHandler_TransactionRun(t *testing.T) { return &types.Result{}, types.NewEVMExecutionError(fmt.Errorf("some sort of error")) }, } - handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, em) + handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, em, nil) coinbase := types.NewAddress(gethCommon.Address{}) @@ -279,7 +279,7 @@ func TestHandler_OpsWithoutEmulator(t *testing.T) { aa, err := handler.NewAddressAllocator(backend, rootAddr) require.NoError(t, err) - handler := handler.NewContractHandler(flowTokenAddress, blockchain, aa, backend, nil) + handler := handler.NewContractHandler(flowTokenAddress, blockchain, aa, backend, nil, nil) foa := handler.AllocateAddress() require.NotNil(t, foa) @@ -370,7 +370,7 @@ func TestHandler_BridgedAccount(t *testing.T) { assertPanic(t, types.IsAUnAuthroizedMethodCallError, func() { em := &testutils.TestEmulator{} - handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, em) + handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, em, nil) account := handler.AccountByAddress(testutils.RandomAddress(t), false) account.Withdraw(types.Balance(1)) @@ -384,7 +384,7 @@ func TestHandler_BridgedAccount(t *testing.T) { }, } - handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, em) + handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, em, nil) account := handler.AccountByAddress(testutils.RandomAddress(t), true) account.Withdraw(types.Balance(1)) @@ -398,7 +398,7 @@ func TestHandler_BridgedAccount(t *testing.T) { }, } - handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, em) + handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, em, nil) account := handler.AccountByAddress(testutils.RandomAddress(t), true) account.Withdraw(types.Balance(0)) @@ -412,7 +412,7 @@ func TestHandler_BridgedAccount(t *testing.T) { }, } - handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, em) + handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, em, nil) account := handler.AccountByAddress(testutils.RandomAddress(t), true) account.Withdraw(types.Balance(0)) @@ -442,7 +442,7 @@ func TestHandler_BridgedAccount(t *testing.T) { }, } - handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, em) + handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, em, nil) account := handler.AccountByAddress(testutils.RandomAddress(t), true) account.Deposit(types.NewFlowTokenVault(1)) @@ -456,7 +456,7 @@ func TestHandler_BridgedAccount(t *testing.T) { }, } - handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, em) + handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, em, nil) account := handler.AccountByAddress(testutils.RandomAddress(t), true) account.Deposit(types.NewFlowTokenVault(1)) @@ -540,6 +540,6 @@ func SetupHandler(t testing.TB, backend types.Backend, rootAddr flow.Address) *h emulator := emulator.NewEmulator(backend, rootAddr) - handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, emulator) + handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, emulator, nil) return handler } diff --git a/fvm/evm/precompiles/arch.go b/fvm/evm/precompiles/arch.go index 81445cb2c7d..b9c670774e1 100644 --- a/fvm/evm/precompiles/arch.go +++ b/fvm/evm/precompiles/arch.go @@ -4,7 +4,8 @@ import ( "encoding/binary" "fmt" - gethVM "github.com/ethereum/go-ethereum/core/vm" + gethCommon "github.com/ethereum/go-ethereum/common" + "github.com/onflow/flow-go/fvm/evm/types" ) var ( @@ -19,7 +20,7 @@ var ( ) type flowBlockHeightCallable struct { - flowBlockHeight uint64 + flowBlockHeightLookUp func() (uint64, error) } func (c *flowBlockHeightCallable) MethodID() MethodID { @@ -34,10 +35,13 @@ func (c *flowBlockHeightCallable) Run(input []byte) ([]byte, error) { if len(input) > 0 { return nil, fmt.Errorf("unexpected input is provided") } - // TODO maybe replace this with what abi encode does + bh, err := c.flowBlockHeightLookUp() + if err != nil { + return nil, err + } encoded := make([]byte, 8) - binary.BigEndian.PutUint64(encoded, c.flowBlockHeight) - return encoded, nil + binary.BigEndian.PutUint64(encoded, bh) + return gethCommon.LeftPadBytes(encoded, 32), nil } type verifyProofCallable struct { @@ -63,10 +67,15 @@ func (c *verifyProofCallable) Run(input []byte) ([]byte, error) { return false32Byte, err } -func GetArchContract(flowBlockHeight uint64, verifyProof func([]byte) (bool, error)) gethVM.PrecompiledContract { - fbh := &flowBlockHeightCallable{flowBlockHeight} +func ArchContract( + address types.Address, + heightProvider func() (uint64, error), + verifyProof func([]byte) (bool, error), +) types.Precompile { + fbh := &flowBlockHeightCallable{heightProvider} vc := &verifyProofCallable{verifyProof} - return GetPrecompileContract( + return multiMethodPrecompileContract( + address, map[MethodID]Callable{ fbh.MethodID(): fbh, vc.MethodID(): vc, diff --git a/fvm/evm/precompiles/precompile.go b/fvm/evm/precompiles/precompile.go index 4189b5ac7b6..432a9e25bd2 100644 --- a/fvm/evm/precompiles/precompile.go +++ b/fvm/evm/precompiles/precompile.go @@ -3,7 +3,7 @@ package precompiles import ( "fmt" - gethVM "github.com/ethereum/go-ethereum/core/vm" + "github.com/onflow/flow-go/fvm/evm/types" ) // This is derived as the first 4 bytes of the Keccak hash of the ASCII form of the signature of the method @@ -17,12 +17,23 @@ type Callable interface { Run(input []byte) ([]byte, error) } -func GetPrecompileContract(callables map[MethodID]Callable) gethVM.PrecompiledContract { - return &Precompile{callables: callables} +func multiMethodPrecompileContract( + address types.Address, + callables map[MethodID]Callable, +) types.Precompile { + return &Precompile{ + callables: callables, + address: address, + } } type Precompile struct { callables map[MethodID]Callable + address types.Address +} + +func (p *Precompile) Address() types.Address { + return p.address } // RequiredPrice calculates the contract gas use diff --git a/fvm/evm/types/emulator.go b/fvm/evm/types/emulator.go index 1310c94759d..750c241d2b8 100644 --- a/fvm/evm/types/emulator.go +++ b/fvm/evm/types/emulator.go @@ -3,7 +3,6 @@ package types import ( "math/big" - gethCommon "github.com/ethereum/go-ethereum/common" gethTypes "github.com/ethereum/go-ethereum/core/types" gethVM "github.com/ethereum/go-ethereum/core/vm" ) @@ -16,6 +15,11 @@ var ( BlockNumberForEVMRules = big.NewInt(1) ) +type Precompile interface { + gethVM.PrecompiledContract + Address() Address +} + // BlockContext holds the context needed for the emulator operations type BlockContext struct { BlockNumber uint64 @@ -24,7 +28,7 @@ type BlockContext struct { GasFeeCollector Address // a set of extra precompiles to be injected - ExtraPrecompiles map[gethCommon.Address]gethVM.PrecompiledContract + ExtraPrecompiles []Precompile } // NewDefaultBlockContext returns a new default block context @@ -33,7 +37,7 @@ func NewDefaultBlockContext(BlockNumber uint64) BlockContext { BlockNumber: BlockNumber, DirectCallBaseGasUsage: DefaultDirectCallBaseGasUsage, DirectCallGasPrice: DefaultDirectCallGasPrice, - ExtraPrecompiles: make(map[gethCommon.Address]gethVM.PrecompiledContract), + ExtraPrecompiles: make([]Precompile, 0), } } diff --git a/fvm/evm/types/handler.go b/fvm/evm/types/handler.go index 3badb5c6175..2db77e4730b 100644 --- a/fvm/evm/types/handler.go +++ b/fvm/evm/types/handler.go @@ -51,8 +51,11 @@ type Backend interface { // AddressAllocator allocates addresses, used by the handler type AddressAllocator interface { - // AllocateAddress allocates an address to be used by a bridged account resource - AllocateAddress() (Address, error) + // AllocateAddress allocates an address to be used by a COA resource + AllocateCOAAddress() (Address, error) + + // AllocateAddress allocates an address by index to be used by a precompile contract + AllocatePrecompileAddress(index uint64) Address } // BlockStore stores the chain of blocks @@ -72,3 +75,10 @@ type BlockStore interface { // ResetBlockProposal resets the block proposal ResetBlockProposal() error } + +// CadenceArchProvider provides some of the functionalities needed to the candence arch +type CadenceArchProvider interface { + FlowBlockHeight() (uint64, error) + + VerifyAccountProof([]byte) (bool, error) +} From 134479125805f5c9cd55462302b9e2d45dfca59b Mon Sep 17 00:00:00 2001 From: ramtinms Date: Wed, 10 Jan 2024 15:40:53 -0800 Subject: [PATCH 04/14] fix address allocation --- fvm/evm/handler/addressAllocator.go | 10 +++++----- fvm/evm/handler/addressAllocator_test.go | 11 ++++++++--- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/fvm/evm/handler/addressAllocator.go b/fvm/evm/handler/addressAllocator.go index 29eb599d830..5d3e4c4bd00 100644 --- a/fvm/evm/handler/addressAllocator.go +++ b/fvm/evm/handler/addressAllocator.go @@ -12,9 +12,9 @@ import ( const ledgerAddressAllocatorKey = "AddressAllocator" var ( - FlowEVMAddressSpacePrefix = []byte{0, 0, 0, 0, 0, 0, 0, 1} - FlowEVMPrecompileAddressPrefix = []byte{0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1} - FlowEVMCOAAddressPrefix = []byte{0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2} + // leading zeros helps with storage + FlowEVMPrecompileAddressPrefix = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} + FlowEVMCOAAddressPrefix = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2} ) type AddressAllocator struct { @@ -59,7 +59,7 @@ func (aa *AddressAllocator) AllocateCOAAddress() (types.Address, error) { func makeCOAAddress(index uint64) types.Address { var addr types.Address - copy(addr[:types.AddressLength], FlowEVMCOAAddressPrefix) + copy(addr[:types.AddressLength-8], FlowEVMCOAAddressPrefix) binary.BigEndian.PutUint64(addr[types.AddressLength-8:], index) return addr } @@ -71,7 +71,7 @@ func (aa *AddressAllocator) AllocatePrecompileAddress(index uint64) types.Addres func makePrecompileAddress(index uint64) types.Address { var addr types.Address - copy(addr[:types.AddressLength], FlowEVMPrecompileAddressPrefix) + copy(addr[:types.AddressLength-8], FlowEVMPrecompileAddressPrefix) binary.BigEndian.PutUint64(addr[types.AddressLength-8:], index) return addr } diff --git a/fvm/evm/handler/addressAllocator_test.go b/fvm/evm/handler/addressAllocator_test.go index dbbf6e0c601..035241588f7 100644 --- a/fvm/evm/handler/addressAllocator_test.go +++ b/fvm/evm/handler/addressAllocator_test.go @@ -19,16 +19,21 @@ func TestAddressAllocator(t *testing.T) { aa, err := handler.NewAddressAllocator(backend, root) require.NoError(t, err) + adr := aa.AllocatePrecompileAddress(3) + require.NoError(t, err) + expectedAddress := types.NewAddress(gethCommon.HexToAddress("0x0000000000000000000000010000000000000003")) + require.Equal(t, expectedAddress, adr) + // test default value fall back - adr, err := aa.AllocateCOAAddress() + adr, err = aa.AllocateCOAAddress() require.NoError(t, err) - expectedAddress := types.NewAddress(gethCommon.HexToAddress("0x00000001000200000001")) + expectedAddress = types.NewAddress(gethCommon.HexToAddress("0x0000000000000000000000020000000000000001")) require.Equal(t, expectedAddress, adr) // continous allocation logic adr, err = aa.AllocateCOAAddress() require.NoError(t, err) - expectedAddress = types.NewAddress(gethCommon.HexToAddress("0x00000001000200000002")) + expectedAddress = types.NewAddress(gethCommon.HexToAddress("0x0000000000000000000000020000000000000002")) require.Equal(t, expectedAddress, adr) }) From 0910c15eaa953024b6039ccf707c926dcccedb81 Mon Sep 17 00:00:00 2001 From: ramtinms Date: Wed, 10 Jan 2024 18:57:51 -0800 Subject: [PATCH 05/14] improve structure --- fvm/evm/emulator/emulator.go | 5 -- fvm/evm/evm.go | 1 + fvm/evm/evm_test.go | 3 - fvm/evm/handler/addressAllocator.go | 12 ++-- fvm/evm/handler/addressAllocator_test.go | 1 - fvm/evm/handler/handler.go | 11 +--- fvm/evm/handler/handler_test.go | 55 +++++++++++++----- fvm/evm/precompiles/arch.go | 72 +++++++----------------- fvm/evm/precompiles/precompile.go | 41 +++++++++----- fvm/evm/precompiles/precompile_test.go | 1 + fvm/evm/testutils/backend.go | 29 ++++++++++ fvm/evm/types/handler.go | 8 +-- 12 files changed, 130 insertions(+), 109 deletions(-) create mode 100644 fvm/evm/precompiles/precompile_test.go diff --git a/fvm/evm/emulator/emulator.go b/fvm/evm/emulator/emulator.go index ce68f322db2..60d9066d638 100644 --- a/fvm/evm/emulator/emulator.go +++ b/fvm/evm/emulator/emulator.go @@ -1,7 +1,6 @@ package emulator import ( - "fmt" "math/big" gethCommon "github.com/ethereum/go-ethereum/common" @@ -303,10 +302,6 @@ func SetupPrecompile(cfg *Config) error { } for k, v := range cfg.ExtraPrecompiles { - _, alreadyExist := precompiles[k] - if alreadyExist { - return fmt.Errorf("can not add a precompile, address already in use") - } precompiles[k] = v } return nil diff --git a/fvm/evm/evm.go b/fvm/evm/evm.go index 800130bc86e..5fbe6108a16 100644 --- a/fvm/evm/evm.go +++ b/fvm/evm/evm.go @@ -42,6 +42,7 @@ func SetupEnvironment( return err } + // TODO replace with a proper contractHandler := handler.NewContractHandler(common.Address(flowToken), bs, aa, backend, em) stdlib.SetupEnvironment(env, contractHandler, service) diff --git a/fvm/evm/evm_test.go b/fvm/evm/evm_test.go index 6108b57aaaf..e34a6172b98 100644 --- a/fvm/evm/evm_test.go +++ b/fvm/evm/evm_test.go @@ -225,11 +225,8 @@ func TestBridgedAccountWithdraw(t *testing.T) { }) } -// TODO: provide proper contract code func TestBridgedAccountDeploy(t *testing.T) { - t.Parallel() - RunWithTestBackend(t, func(backend *testutils.TestBackend) { RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { tc := GetStorageTestContract(t) diff --git a/fvm/evm/handler/addressAllocator.go b/fvm/evm/handler/addressAllocator.go index 5d3e4c4bd00..a089e91803e 100644 --- a/fvm/evm/handler/addressAllocator.go +++ b/fvm/evm/handler/addressAllocator.go @@ -12,7 +12,9 @@ import ( const ledgerAddressAllocatorKey = "AddressAllocator" var ( - // leading zeros helps with storage + // prefixes: + // the first 12 bytes of addresses allocation + // leading zeros helps with storage and all zero is reserved for the EVM precompiles FlowEVMPrecompileAddressPrefix = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} FlowEVMCOAAddressPrefix = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2} ) @@ -44,7 +46,7 @@ func (aa *AddressAllocator) AllocateCOAAddress() (types.Address, error) { uuid = binary.BigEndian.Uint64(data) } - target := makeCOAAddress(uuid) + target := MakeCOAAddress(uuid) // store new uuid newData := make([]byte, 8) @@ -57,7 +59,7 @@ func (aa *AddressAllocator) AllocateCOAAddress() (types.Address, error) { return target, nil } -func makeCOAAddress(index uint64) types.Address { +func MakeCOAAddress(index uint64) types.Address { var addr types.Address copy(addr[:types.AddressLength-8], FlowEVMCOAAddressPrefix) binary.BigEndian.PutUint64(addr[types.AddressLength-8:], index) @@ -65,11 +67,11 @@ func makeCOAAddress(index uint64) types.Address { } func (aa *AddressAllocator) AllocatePrecompileAddress(index uint64) types.Address { - target := makePrecompileAddress(index) + target := MakePrecompileAddress(index) return target } -func makePrecompileAddress(index uint64) types.Address { +func MakePrecompileAddress(index uint64) types.Address { var addr types.Address copy(addr[:types.AddressLength-8], FlowEVMPrecompileAddressPrefix) binary.BigEndian.PutUint64(addr[types.AddressLength-8:], index) diff --git a/fvm/evm/handler/addressAllocator_test.go b/fvm/evm/handler/addressAllocator_test.go index 035241588f7..03794baea9a 100644 --- a/fvm/evm/handler/addressAllocator_test.go +++ b/fvm/evm/handler/addressAllocator_test.go @@ -20,7 +20,6 @@ func TestAddressAllocator(t *testing.T) { require.NoError(t, err) adr := aa.AllocatePrecompileAddress(3) - require.NoError(t, err) expectedAddress := types.NewAddress(gethCommon.HexToAddress("0x0000000000000000000000010000000000000003")) require.Equal(t, expectedAddress, adr) diff --git a/fvm/evm/handler/handler.go b/fvm/evm/handler/handler.go index 1a643dddddc..63f0e346e16 100644 --- a/fvm/evm/handler/handler.go +++ b/fvm/evm/handler/handler.go @@ -44,7 +44,6 @@ func NewContractHandler( addressAllocator types.AddressAllocator, backend types.Backend, emulator types.Emulator, - archProvider types.CadenceArchProvider, ) *ContractHandler { return &ContractHandler{ flowTokenAddress: flowTokenAddress, @@ -52,22 +51,18 @@ func NewContractHandler( addressAllocator: addressAllocator, backend: backend, emulator: emulator, - precompiles: getPrecompiles(addressAllocator, archProvider), + precompiles: getPrecompiles(addressAllocator, backend), } } func getPrecompiles( addressAllocator types.AddressAllocator, - archProvider types.CadenceArchProvider, + backend types.Backend, ) []types.Precompile { - if archProvider == nil || addressAllocator == nil { - return nil - } archAddress := addressAllocator.AllocatePrecompileAddress(1) archContract := precompiles.ArchContract( archAddress, - archProvider.FlowBlockHeight, - archProvider.VerifyAccountProof, + backend.GetCurrentBlockHeight, ) return []types.Precompile{archContract} } diff --git a/fvm/evm/handler/handler_test.go b/fvm/evm/handler/handler_test.go index 0b37e501824..49e2adb814b 100644 --- a/fvm/evm/handler/handler_test.go +++ b/fvm/evm/handler/handler_test.go @@ -24,6 +24,7 @@ import ( "github.com/onflow/flow-go/fvm/errors" "github.com/onflow/flow-go/fvm/evm/emulator" "github.com/onflow/flow-go/fvm/evm/handler" + "github.com/onflow/flow-go/fvm/evm/precompiles" "github.com/onflow/flow-go/fvm/evm/testutils" "github.com/onflow/flow-go/fvm/evm/types" "github.com/onflow/flow-go/fvm/systemcontracts" @@ -65,7 +66,7 @@ func TestHandler_TransactionRun(t *testing.T) { return result, nil }, } - handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, em, nil) + handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, em) coinbase := types.NewAddress(gethCommon.Address{}) @@ -143,7 +144,7 @@ func TestHandler_TransactionRun(t *testing.T) { return &types.Result{}, types.NewEVMExecutionError(fmt.Errorf("some sort of error")) }, } - handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, em, nil) + handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, em) coinbase := types.NewAddress(gethCommon.Address{}) @@ -279,12 +280,12 @@ func TestHandler_OpsWithoutEmulator(t *testing.T) { aa, err := handler.NewAddressAllocator(backend, rootAddr) require.NoError(t, err) - handler := handler.NewContractHandler(flowTokenAddress, blockchain, aa, backend, nil, nil) + h := handler.NewContractHandler(flowTokenAddress, blockchain, aa, backend, nil) - foa := handler.AllocateAddress() + foa := h.AllocateAddress() require.NotNil(t, foa) - expectedAddress := types.NewAddress(gethCommon.HexToAddress("0x00000000000000000001")) + expectedAddress := handler.MakeCOAAddress(1) require.Equal(t, expectedAddress, foa) }) }) @@ -355,8 +356,6 @@ func TestHandler_BridgedAccount(t *testing.T) { }) t.Run("test withdraw (unhappy case)", func(t *testing.T) { - t.Parallel() - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { testutils.RunWithEOATestAccount(t, backend, rootAddr, func(eoa *testutils.EOATestAccount) { @@ -370,7 +369,7 @@ func TestHandler_BridgedAccount(t *testing.T) { assertPanic(t, types.IsAUnAuthroizedMethodCallError, func() { em := &testutils.TestEmulator{} - handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, em, nil) + handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, em) account := handler.AccountByAddress(testutils.RandomAddress(t), false) account.Withdraw(types.Balance(1)) @@ -384,7 +383,7 @@ func TestHandler_BridgedAccount(t *testing.T) { }, } - handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, em, nil) + handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, em) account := handler.AccountByAddress(testutils.RandomAddress(t), true) account.Withdraw(types.Balance(1)) @@ -398,7 +397,7 @@ func TestHandler_BridgedAccount(t *testing.T) { }, } - handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, em, nil) + handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, em) account := handler.AccountByAddress(testutils.RandomAddress(t), true) account.Withdraw(types.Balance(0)) @@ -412,7 +411,7 @@ func TestHandler_BridgedAccount(t *testing.T) { }, } - handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, em, nil) + handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, em) account := handler.AccountByAddress(testutils.RandomAddress(t), true) account.Withdraw(types.Balance(0)) @@ -442,7 +441,7 @@ func TestHandler_BridgedAccount(t *testing.T) { }, } - handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, em, nil) + handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, em) account := handler.AccountByAddress(testutils.RandomAddress(t), true) account.Deposit(types.NewFlowTokenVault(1)) @@ -456,7 +455,7 @@ func TestHandler_BridgedAccount(t *testing.T) { }, } - handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, em, nil) + handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, em) account := handler.AccountByAddress(testutils.RandomAddress(t), true) account.Deposit(types.NewFlowTokenVault(1)) @@ -506,6 +505,34 @@ func TestHandler_BridgedAccount(t *testing.T) { }) }) + t.Run("test precompiled", func(t *testing.T) { + t.Parallel() + + testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + blockHeight := uint64(123) + backend.GetCurrentBlockHeightFunc = func() (uint64, error) { + return blockHeight, nil + } + testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { + h := SetupHandler(t, backend, rootAddr) + + foa := h.AccountByAddress(h.AllocateAddress(), true) + require.NotNil(t, foa) + + // deposit 10000 flow + orgBalance, err := types.NewBalanceFromAttoFlow(new(big.Int).Mul(big.NewInt(1e18), big.NewInt(10000))) + require.NoError(t, err) + vault := types.NewFlowTokenVault(orgBalance) + foa.Deposit(vault) + + arch := handler.MakePrecompileAddress(1) + + ret := foa.Call(arch, precompiles.FlowBlockHeightFuncSig[:], math.MaxUint64, types.Balance(0)) + require.Equal(t, big.NewInt(int64(blockHeight)), new(big.Int).SetBytes(ret)) + }) + }) + }) + // TODO add test with test emulator for unhappy cases (emulator) } @@ -540,6 +567,6 @@ func SetupHandler(t testing.TB, backend types.Backend, rootAddr flow.Address) *h emulator := emulator.NewEmulator(backend, rootAddr) - handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, emulator, nil) + handler := handler.NewContractHandler(flowTokenAddress, bs, aa, backend, emulator) return handler } diff --git a/fvm/evm/precompiles/arch.go b/fvm/evm/precompiles/arch.go index b9c670774e1..a84b29a4231 100644 --- a/fvm/evm/precompiles/arch.go +++ b/fvm/evm/precompiles/arch.go @@ -9,29 +9,36 @@ import ( ) var ( - FlowBlockHeightMethodID = [4]byte{1, 2, 3, 4} // TODO fill in with proper value - FlowBlockHeightFixedGas = uint64(0) // TODO update me with a proper value - - VerifyProofMethodID = [4]byte{3, 4, 5, 6} // TODO fill in with proper value - VerifyProofFixedGas = uint64(0) // TODO update me with a proper value - - true32Byte = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} - false32Byte = make([]byte, 32) + FlowBlockHeightFuncSig = ComputeFunctionSignature("flowBlockHeight", nil) + FlowBlockHeightFixedGas = uint64(0) // TODO update me with a proper value ) -type flowBlockHeightCallable struct { +func ArchContract( + address types.Address, + heightProvider func() (uint64, error), +) types.Precompile { + fbh := &flowBlockHeightFunction{heightProvider} + return multiMethodPrecompileContract( + address, + map[FunctionSignature]Function{ + fbh.FunctionSignature(): fbh, + }, + ) +} + +type flowBlockHeightFunction struct { flowBlockHeightLookUp func() (uint64, error) } -func (c *flowBlockHeightCallable) MethodID() MethodID { - return FlowBlockHeightMethodID +func (c *flowBlockHeightFunction) FunctionSignature() FunctionSignature { + return FlowBlockHeightFuncSig } -func (c *flowBlockHeightCallable) ComputeGas(input []byte) uint64 { +func (c *flowBlockHeightFunction) ComputeGas(input []byte) uint64 { return FlowBlockHeightFixedGas } -func (c *flowBlockHeightCallable) Run(input []byte) ([]byte, error) { +func (c *flowBlockHeightFunction) Run(input []byte) ([]byte, error) { if len(input) > 0 { return nil, fmt.Errorf("unexpected input is provided") } @@ -43,42 +50,3 @@ func (c *flowBlockHeightCallable) Run(input []byte) ([]byte, error) { binary.BigEndian.PutUint64(encoded, bh) return gethCommon.LeftPadBytes(encoded, 32), nil } - -type verifyProofCallable struct { - verifyProof func([]byte) (bool, error) -} - -func (c *verifyProofCallable) MethodID() MethodID { - return VerifyProofMethodID -} - -func (c *verifyProofCallable) ComputeGas(input []byte) uint64 { - return VerifyProofFixedGas -} - -func (c *verifyProofCallable) Run(input []byte) ([]byte, error) { - res, err := c.verifyProof(input) - if err != nil { - return nil, err - } - if res { - return true32Byte, err - } - return false32Byte, err -} - -func ArchContract( - address types.Address, - heightProvider func() (uint64, error), - verifyProof func([]byte) (bool, error), -) types.Precompile { - fbh := &flowBlockHeightCallable{heightProvider} - vc := &verifyProofCallable{verifyProof} - return multiMethodPrecompileContract( - address, - map[MethodID]Callable{ - fbh.MethodID(): fbh, - vc.MethodID(): vc, - }, - ) -} diff --git a/fvm/evm/precompiles/precompile.go b/fvm/evm/precompiles/precompile.go index 432a9e25bd2..81d1bc731d4 100644 --- a/fvm/evm/precompiles/precompile.go +++ b/fvm/evm/precompiles/precompile.go @@ -2,15 +2,28 @@ package precompiles import ( "fmt" + "strings" + + gethCrypto "github.com/ethereum/go-ethereum/crypto" "github.com/onflow/flow-go/fvm/evm/types" ) // This is derived as the first 4 bytes of the Keccak hash of the ASCII form of the signature of the method -type MethodID [4]byte +type FunctionSignature [4]byte + +// ComputeFunctionSignature computes the function signture +// given the canonical name of function and args. +// for example the canonical format for int is int256 +func ComputeFunctionSignature(name string, args []string) FunctionSignature { + var sig FunctionSignature + input := fmt.Sprintf("%v(%v)", name, strings.Join(args, ",")) + copy(sig[0:4], gethCrypto.Keccak256([]byte(input))[:4]) + return sig +} -type Callable interface { - MethodID() MethodID +type Function interface { + FunctionSignature() FunctionSignature ComputeGas(input []byte) uint64 @@ -19,16 +32,16 @@ type Callable interface { func multiMethodPrecompileContract( address types.Address, - callables map[MethodID]Callable, + functions map[FunctionSignature]Function, ) types.Precompile { return &Precompile{ - callables: callables, + functions: functions, address: address, } } type Precompile struct { - callables map[MethodID]Callable + functions map[FunctionSignature]Function address types.Address } @@ -41,8 +54,8 @@ func (p *Precompile) RequiredGas(input []byte) uint64 { if len(input) < 4 { return 0 } - mID, data := splitMethodID(input) - callable, found := p.callables[mID] + sig, data := splitFunctionSignature(input) + callable, found := p.functions[sig] if !found { return 0 } @@ -54,16 +67,16 @@ func (p *Precompile) Run(input []byte) ([]byte, error) { if len(input) < 4 { return nil, fmt.Errorf("invalid method") // TODO return the right error based on geth } - mID, data := splitMethodID(input) - callable, found := p.callables[mID] + sig, data := splitFunctionSignature(input) + callable, found := p.functions[sig] if !found { return nil, fmt.Errorf("invalid method") // TODO return the right error based on geth } return callable.Run(data) } -func splitMethodID(input []byte) (MethodID, []byte) { - var methodID MethodID - copy(methodID[:], input[0:4]) - return methodID, input[4:] +func splitFunctionSignature(input []byte) (FunctionSignature, []byte) { + var funcSig FunctionSignature + copy(funcSig[:], input[0:4]) + return funcSig, input[4:] } diff --git a/fvm/evm/precompiles/precompile_test.go b/fvm/evm/precompiles/precompile_test.go new file mode 100644 index 00000000000..43d27c26ad9 --- /dev/null +++ b/fvm/evm/precompiles/precompile_test.go @@ -0,0 +1 @@ +package precompiles_test diff --git a/fvm/evm/testutils/backend.go b/fvm/evm/testutils/backend.go index fc08d84aad8..8c73fbf02f3 100644 --- a/fvm/evm/testutils/backend.go +++ b/fvm/evm/testutils/backend.go @@ -9,11 +9,13 @@ import ( "github.com/onflow/atree" "github.com/onflow/cadence" jsoncdc "github.com/onflow/cadence/encoding/json" + "github.com/onflow/cadence/runtime" "github.com/onflow/cadence/runtime/common" "github.com/stretchr/testify/require" "golang.org/x/exp/maps" "github.com/onflow/flow-go/fvm/environment" + "github.com/onflow/flow-go/fvm/evm/types" "github.com/onflow/flow-go/fvm/meter" "github.com/onflow/flow-go/model/flow" ) @@ -33,6 +35,7 @@ func RunWithTestBackend(t testing.TB, f func(*TestBackend)) { TestValueStore: GetSimpleValueStore(), testEventEmitter: getSimpleEventEmitter(), testMeter: getSimpleMeter(), + TestBlockInfo: &TestBlockInfo{}, } f(tb) } @@ -153,8 +156,11 @@ type TestBackend struct { *TestValueStore *testMeter *testEventEmitter + *TestBlockInfo } +var _ types.Backend = &TestBackend{} + func (tb *TestBackend) TotalStorageSize() int { if tb.TotalStorageSizeFunc == nil { panic("method not set") @@ -376,3 +382,26 @@ func (vs *testEventEmitter) Reset() { } vs.reset() } + +type TestBlockInfo struct { + GetCurrentBlockHeightFunc func() (uint64, error) + GetBlockAtHeightFunc func(height uint64) (runtime.Block, bool, error) +} + +var _ environment.BlockInfo = &TestBlockInfo{} + +// GetCurrentBlockHeight returns the current block height. +func (tb *TestBlockInfo) GetCurrentBlockHeight() (uint64, error) { + if tb.GetCurrentBlockHeightFunc == nil { + panic("GetCurrentBlockHeight method is not set") + } + return tb.GetCurrentBlockHeightFunc() +} + +// GetBlockAtHeight returns the block at the given height. +func (tb *TestBlockInfo) GetBlockAtHeight(height uint64) (runtime.Block, bool, error) { + if tb.GetBlockAtHeightFunc == nil { + panic("GetBlockAtHeight method is not set") + } + return tb.GetBlockAtHeightFunc(height) +} diff --git a/fvm/evm/types/handler.go b/fvm/evm/types/handler.go index 2db77e4730b..bfe187234e8 100644 --- a/fvm/evm/types/handler.go +++ b/fvm/evm/types/handler.go @@ -47,6 +47,7 @@ type Backend interface { environment.ValueStore environment.Meter environment.EventEmitter + environment.BlockInfo } // AddressAllocator allocates addresses, used by the handler @@ -75,10 +76,3 @@ type BlockStore interface { // ResetBlockProposal resets the block proposal ResetBlockProposal() error } - -// CadenceArchProvider provides some of the functionalities needed to the candence arch -type CadenceArchProvider interface { - FlowBlockHeight() (uint64, error) - - VerifyAccountProof([]byte) (bool, error) -} From e56361a0b940bd8d31911bb992f53b078e3d91ae Mon Sep 17 00:00:00 2001 From: ramtinms Date: Wed, 10 Jan 2024 20:28:33 -0800 Subject: [PATCH 06/14] add tests --- fvm/evm/handler/handler_test.go | 3 +- fvm/evm/precompiles/arch.go | 8 ++- fvm/evm/precompiles/precompile.go | 53 ++++++++------------ fvm/evm/precompiles/precompile_test.go | 69 ++++++++++++++++++++++++++ fvm/evm/precompiles/signature.go | 33 ++++++++++++ fvm/evm/precompiles/signature_test.go | 20 ++++++++ 6 files changed, 146 insertions(+), 40 deletions(-) create mode 100644 fvm/evm/precompiles/signature.go create mode 100644 fvm/evm/precompiles/signature_test.go diff --git a/fvm/evm/handler/handler_test.go b/fvm/evm/handler/handler_test.go index 49e2adb814b..0463481aaef 100644 --- a/fvm/evm/handler/handler_test.go +++ b/fvm/evm/handler/handler_test.go @@ -505,7 +505,7 @@ func TestHandler_BridgedAccount(t *testing.T) { }) }) - t.Run("test precompiled", func(t *testing.T) { + t.Run("test call to cadence arch", func(t *testing.T) { t.Parallel() testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { @@ -519,7 +519,6 @@ func TestHandler_BridgedAccount(t *testing.T) { foa := h.AccountByAddress(h.AllocateAddress(), true) require.NotNil(t, foa) - // deposit 10000 flow orgBalance, err := types.NewBalanceFromAttoFlow(new(big.Int).Mul(big.NewInt(1e18), big.NewInt(10000))) require.NoError(t, err) vault := types.NewFlowTokenVault(orgBalance) diff --git a/fvm/evm/precompiles/arch.go b/fvm/evm/precompiles/arch.go index a84b29a4231..c7974afcd92 100644 --- a/fvm/evm/precompiles/arch.go +++ b/fvm/evm/precompiles/arch.go @@ -13,16 +13,14 @@ var ( FlowBlockHeightFixedGas = uint64(0) // TODO update me with a proper value ) +// ArchContract return a procompile for the Cadence Arch contract func ArchContract( address types.Address, heightProvider func() (uint64, error), ) types.Precompile { - fbh := &flowBlockHeightFunction{heightProvider} - return multiMethodPrecompileContract( + return MultiFunctionPrecompileContract( address, - map[FunctionSignature]Function{ - fbh.FunctionSignature(): fbh, - }, + []Function{&flowBlockHeightFunction{heightProvider}}, ) } diff --git a/fvm/evm/precompiles/precompile.go b/fvm/evm/precompiles/precompile.go index 81d1bc731d4..dcd7dab3822 100644 --- a/fvm/evm/precompiles/precompile.go +++ b/fvm/evm/precompiles/precompile.go @@ -2,59 +2,52 @@ package precompiles import ( "fmt" - "strings" - - gethCrypto "github.com/ethereum/go-ethereum/crypto" "github.com/onflow/flow-go/fvm/evm/types" ) -// This is derived as the first 4 bytes of the Keccak hash of the ASCII form of the signature of the method -type FunctionSignature [4]byte - -// ComputeFunctionSignature computes the function signture -// given the canonical name of function and args. -// for example the canonical format for int is int256 -func ComputeFunctionSignature(name string, args []string) FunctionSignature { - var sig FunctionSignature - input := fmt.Sprintf("%v(%v)", name, strings.Join(args, ",")) - copy(sig[0:4], gethCrypto.Keccak256([]byte(input))[:4]) - return sig -} - +// Function is an interface for a function in a multi-function precompile contract type Function interface { + // FunctionSignature returns the function signature for this function FunctionSignature() FunctionSignature + // ComputeGas computes the gas needed for the given input ComputeGas(input []byte) uint64 + // Run runs the function on the given data Run(input []byte) ([]byte, error) } -func multiMethodPrecompileContract( +// MultiFunctionPrecompileContract constructs a multi-function precompile smart contract +func MultiFunctionPrecompileContract( address types.Address, - functions map[FunctionSignature]Function, + functions []Function, ) types.Precompile { - return &Precompile{ - functions: functions, + pc := &precompile{ + functions: make(map[FunctionSignature]Function), address: address, } + for _, f := range functions { + pc.functions[f.FunctionSignature()] = f + } + return pc } -type Precompile struct { - functions map[FunctionSignature]Function +type precompile struct { address types.Address + functions map[FunctionSignature]Function } -func (p *Precompile) Address() types.Address { +func (p *precompile) Address() types.Address { return p.address } // RequiredPrice calculates the contract gas use -func (p *Precompile) RequiredGas(input []byte) uint64 { +func (p *precompile) RequiredGas(input []byte) uint64 { if len(input) < 4 { return 0 } - sig, data := splitFunctionSignature(input) + sig, data := SplitFunctionSignature(input) callable, found := p.functions[sig] if !found { return 0 @@ -63,20 +56,14 @@ func (p *Precompile) RequiredGas(input []byte) uint64 { } // Run runs the precompiled contract -func (p *Precompile) Run(input []byte) ([]byte, error) { +func (p *precompile) Run(input []byte) ([]byte, error) { if len(input) < 4 { return nil, fmt.Errorf("invalid method") // TODO return the right error based on geth } - sig, data := splitFunctionSignature(input) + sig, data := SplitFunctionSignature(input) callable, found := p.functions[sig] if !found { return nil, fmt.Errorf("invalid method") // TODO return the right error based on geth } return callable.Run(data) } - -func splitFunctionSignature(input []byte) (FunctionSignature, []byte) { - var funcSig FunctionSignature - copy(funcSig[:], input[0:4]) - return funcSig, input[4:] -} diff --git a/fvm/evm/precompiles/precompile_test.go b/fvm/evm/precompiles/precompile_test.go index 43d27c26ad9..05e71eb0576 100644 --- a/fvm/evm/precompiles/precompile_test.go +++ b/fvm/evm/precompiles/precompile_test.go @@ -1 +1,70 @@ package precompiles_test + +import ( + "testing" + + "github.com/onflow/flow-go/fvm/evm/precompiles" + "github.com/onflow/flow-go/fvm/evm/testutils" + "github.com/stretchr/testify/require" +) + +func TestMutiFunctionContract(t *testing.T) { + address := testutils.RandomAddress(t) + sig := precompiles.FunctionSignature{1, 2, 3, 4} + data := "data" + input := append(sig[:], data...) + gas := uint64(20) + output := []byte("output") + + pc := precompiles.MultiFunctionPrecompileContract(address, []precompiles.Function{ + &mockedFunction{ + FunctionSignatureFunc: func() precompiles.FunctionSignature { + return sig + }, + ComputeGasFunc: func(inp []byte) uint64 { + require.Equal(t, []byte(data), inp) + return gas + }, + RunFunc: func(inp []byte) ([]byte, error) { + require.Equal(t, []byte(data), inp) + return output, nil + }, + }}) + + require.Equal(t, address, pc.Address()) + require.Equal(t, gas, pc.RequiredGas(input)) + ret, err := pc.Run(input) + require.NoError(t, err) + require.Equal(t, output, ret) + + input2 := []byte("non existing signature and data") + _, err = pc.Run(input2) + require.Error(t, err) +} + +type mockedFunction struct { + FunctionSignatureFunc func() precompiles.FunctionSignature + ComputeGasFunc func(input []byte) uint64 + RunFunc func(input []byte) ([]byte, error) +} + +func (mf *mockedFunction) FunctionSignature() precompiles.FunctionSignature { + if mf.FunctionSignatureFunc == nil { + panic("method not set for mocked function") + } + return mf.FunctionSignatureFunc() +} + +func (mf *mockedFunction) ComputeGas(input []byte) uint64 { + if mf.ComputeGasFunc == nil { + panic("method not set for mocked function") + } + return mf.ComputeGasFunc(input) +} + +func (mf *mockedFunction) Run(input []byte) ([]byte, error) { + if mf.RunFunc == nil { + panic("method not set for mocked function") + } + return mf.RunFunc(input) +} diff --git a/fvm/evm/precompiles/signature.go b/fvm/evm/precompiles/signature.go new file mode 100644 index 00000000000..b37184d057b --- /dev/null +++ b/fvm/evm/precompiles/signature.go @@ -0,0 +1,33 @@ +package precompiles + +import ( + "fmt" + "strings" + + gethCrypto "github.com/ethereum/go-ethereum/crypto" +) + +// This is derived as the first 4 bytes of the Keccak hash of the ASCII form of the signature of the method +type FunctionSignature [4]byte + +func (fs FunctionSignature) Bytes() []byte { + return fs[:] +} + +// ComputeFunctionSignature computes the function signture +// given the canonical name of function and args. +// for example the canonical format for int is int256 +func ComputeFunctionSignature(name string, args []string) FunctionSignature { + var sig FunctionSignature + input := fmt.Sprintf("%v(%v)", name, strings.Join(args, ",")) + copy(sig[0:4], gethCrypto.Keccak256([]byte(input))[:4]) + return sig +} + +// SplitFunctionSignature splits the function signature from input data and +// returns the rest of the data +func SplitFunctionSignature(input []byte) (FunctionSignature, []byte) { + var funcSig FunctionSignature + copy(funcSig[:], input[0:4]) + return funcSig, input[4:] +} diff --git a/fvm/evm/precompiles/signature_test.go b/fvm/evm/precompiles/signature_test.go new file mode 100644 index 00000000000..3db898d182d --- /dev/null +++ b/fvm/evm/precompiles/signature_test.go @@ -0,0 +1,20 @@ +package precompiles_test + +import ( + "testing" + + gethCrypto "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/require" + + "github.com/onflow/flow-go/fvm/evm/precompiles" +) + +func TestFunctionSignatures(t *testing.T) { + + expected := gethCrypto.Keccak256([]byte("test()"))[:4] + require.Equal(t, expected, precompiles.ComputeFunctionSignature("test", nil).Bytes()) + + expected = gethCrypto.Keccak256([]byte("test(uint32,uint16)"))[:4] + require.Equal(t, expected, + precompiles.ComputeFunctionSignature("test", []string{"uint32", "uint16"}).Bytes()) +} From 844c8ef6fca97c411279c81b2c26d68349efa0e2 Mon Sep 17 00:00:00 2001 From: ramtinms Date: Wed, 10 Jan 2024 20:50:05 -0800 Subject: [PATCH 07/14] update arch gas --- fvm/evm/precompiles/arch.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fvm/evm/precompiles/arch.go b/fvm/evm/precompiles/arch.go index c7974afcd92..26c35a1db88 100644 --- a/fvm/evm/precompiles/arch.go +++ b/fvm/evm/precompiles/arch.go @@ -9,8 +9,9 @@ import ( ) var ( - FlowBlockHeightFuncSig = ComputeFunctionSignature("flowBlockHeight", nil) - FlowBlockHeightFixedGas = uint64(0) // TODO update me with a proper value + FlowBlockHeightFuncSig = ComputeFunctionSignature("flowBlockHeight", nil) + // TODO update me with a higher value if needed + FlowBlockHeightFixedGas = uint64(1) ) // ArchContract return a procompile for the Cadence Arch contract From 19029724cfb2dd6a17670582241ec4043d401a0f Mon Sep 17 00:00:00 2001 From: ramtinms Date: Wed, 10 Jan 2024 21:05:23 -0800 Subject: [PATCH 08/14] add test for arch --- fvm/evm/precompiles/arch_test.go | 34 ++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 fvm/evm/precompiles/arch_test.go diff --git a/fvm/evm/precompiles/arch_test.go b/fvm/evm/precompiles/arch_test.go new file mode 100644 index 00000000000..3c292a22d74 --- /dev/null +++ b/fvm/evm/precompiles/arch_test.go @@ -0,0 +1,34 @@ +package precompiles_test + +import ( + "testing" + + "github.com/onflow/flow-go/fvm/evm/precompiles" + "github.com/onflow/flow-go/fvm/evm/testutils" + "github.com/stretchr/testify/require" +) + +func TestArchContract(t *testing.T) { + address := testutils.RandomAddress(t) + + height := uint64(12) + pc := precompiles.ArchContract( + address, + func() (uint64, error) { + return height, nil + }, + ) + + input := precompiles.FlowBlockHeightFuncSig.Bytes() + require.Equal(t, address, pc.Address()) + require.Equal(t, precompiles.FlowBlockHeightFixedGas, pc.RequiredGas(input)) + ret, err := pc.Run(input) + require.NoError(t, err) + + expected := make([]byte, 32) + expected[31] = 12 + require.Equal(t, expected, ret) + + _, err = pc.Run([]byte{1, 2, 3}) + require.Error(t, err) +} From d886b0bb10eac7e280894ce412db27d514c34706 Mon Sep 17 00:00:00 2001 From: ramtinms Date: Thu, 11 Jan 2024 11:29:40 -0800 Subject: [PATCH 09/14] make linter happy again --- fvm/evm/emulator/config.go | 42 +++++++++++++------------- fvm/evm/emulator/emulator.go | 2 +- fvm/evm/precompiles/arch.go | 1 + fvm/evm/precompiles/arch_test.go | 3 +- fvm/evm/precompiles/precompile_test.go | 3 +- 5 files changed, 27 insertions(+), 24 deletions(-) diff --git a/fvm/evm/emulator/config.go b/fvm/evm/emulator/config.go index 16b4ed607f4..5593ae80d41 100644 --- a/fvm/evm/emulator/config.go +++ b/fvm/evm/emulator/config.go @@ -4,13 +4,12 @@ import ( "math" "math/big" - "github.com/ethereum/go-ethereum/common" gethCommon "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/vm" + gethCore "github.com/ethereum/go-ethereum/core" gethVM "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/params" + gethCrypto "github.com/ethereum/go-ethereum/crypto" + gethParams "github.com/ethereum/go-ethereum/params" + "github.com/onflow/flow-go/fvm/evm/types" ) @@ -24,20 +23,20 @@ var ( // Config sets the required parameters type Config struct { // Chain Config - ChainConfig *params.ChainConfig + ChainConfig *gethParams.ChainConfig // EVM config - EVMConfig vm.Config + EVMConfig gethVM.Config // block context - BlockContext *vm.BlockContext + BlockContext *gethVM.BlockContext // transaction context - TxContext *vm.TxContext + TxContext *gethVM.TxContext // base unit of gas for direct calls DirectCallBaseGasUsage uint64 // a set of extra precompiles to be injected ExtraPrecompiles map[gethCommon.Address]gethVM.PrecompiledContract } -func (c *Config) ChainRules() params.Rules { +func (c *Config) ChainRules() gethParams.Rules { return c.ChainConfig.Rules( c.BlockContext.BlockNumber, c.BlockContext.Random != nil, @@ -51,7 +50,7 @@ func (c *Config) ChainRules() params.Rules { // For the future changes of EVM, we need to update the EVM go mod version // and set a proper height for the specific release based on the Flow EVM heights // so it could gets activated at a desired time. -var DefaultChainConfig = ¶ms.ChainConfig{ +var DefaultChainConfig = &gethParams.ChainConfig{ ChainID: FlowEVMTestnetChainID, // default is testnet // Fork scheduling based on block heights @@ -78,20 +77,20 @@ var DefaultChainConfig = ¶ms.ChainConfig{ func defaultConfig() *Config { return &Config{ ChainConfig: DefaultChainConfig, - EVMConfig: vm.Config{ + EVMConfig: gethVM.Config{ NoBaseFee: true, }, - TxContext: &vm.TxContext{ + TxContext: &gethVM.TxContext{ GasPrice: new(big.Int), BlobFeeCap: new(big.Int), }, - BlockContext: &vm.BlockContext{ - CanTransfer: core.CanTransfer, - Transfer: core.Transfer, + BlockContext: &gethVM.BlockContext{ + CanTransfer: gethCore.CanTransfer, + Transfer: gethCore.Transfer, GasLimit: BlockLevelGasLimit, // block gas limit BaseFee: big.NewInt(0), - GetHash: func(n uint64) common.Hash { // default returns some random hash values - return common.BytesToHash(crypto.Keccak256([]byte(new(big.Int).SetUint64(n).String()))) + GetHash: func(n uint64) gethCommon.Hash { // default returns some random hash values + return gethCommon.BytesToHash(gethCrypto.Keccak256([]byte(new(big.Int).SetUint64(n).String()))) }, }, ExtraPrecompiles: make(map[gethCommon.Address]gethVM.PrecompiledContract), @@ -127,7 +126,7 @@ func WithMainnetChainID() Option { } // WithOrigin sets the origin of the transaction (signer) -func WithOrigin(origin common.Address) Option { +func WithOrigin(origin gethCommon.Address) Option { return func(c *Config) *Config { c.TxContext.Origin = origin return c @@ -151,7 +150,7 @@ func WithGasLimit(gasLimit uint64) Option { } // WithCoinbase sets the coinbase of the block where the fees are collected in -func WithCoinbase(coinbase common.Address) Option { +func WithCoinbase(coinbase gethCommon.Address) Option { return func(c *Config) *Config { c.BlockContext.Coinbase = coinbase return c @@ -175,7 +174,7 @@ func WithBlockTime(time uint64) Option { } // WithGetBlockHashFunction sets the functionality to look up block hash by height -func WithGetBlockHashFunction(getHash vm.GetHashFunc) Option { +func WithGetBlockHashFunction(getHash gethVM.GetHashFunc) Option { return func(c *Config) *Config { c.BlockContext.GetHash = getHash return c @@ -190,6 +189,7 @@ func WithDirectCallBaseGasUsage(gas uint64) Option { } } +// WithExtraPrecompiles appends precompile list with extra precompiles func WithExtraPrecompiles(precompiles []types.Precompile) Option { return func(c *Config) *Config { for _, pc := range precompiles { diff --git a/fvm/evm/emulator/emulator.go b/fvm/evm/emulator/emulator.go index 60d9066d638..1c9df871cbf 100644 --- a/fvm/evm/emulator/emulator.go +++ b/fvm/evm/emulator/emulator.go @@ -287,6 +287,7 @@ func (proc *procedure) run(msg *gethCore.Message, txType uint8) (*types.Result, func SetupPrecompile(cfg *Config) error { rules := cfg.ChainRules() + // captures the pointer to the map that has to be augmented var precompiles map[gethCommon.Address]gethVM.PrecompiledContract switch { case rules.IsCancun: @@ -300,7 +301,6 @@ func SetupPrecompile(cfg *Config) error { default: precompiles = gethVM.PrecompiledContractsHomestead } - for k, v := range cfg.ExtraPrecompiles { precompiles[k] = v } diff --git a/fvm/evm/precompiles/arch.go b/fvm/evm/precompiles/arch.go index 26c35a1db88..057ac784b6e 100644 --- a/fvm/evm/precompiles/arch.go +++ b/fvm/evm/precompiles/arch.go @@ -5,6 +5,7 @@ import ( "fmt" gethCommon "github.com/ethereum/go-ethereum/common" + "github.com/onflow/flow-go/fvm/evm/types" ) diff --git a/fvm/evm/precompiles/arch_test.go b/fvm/evm/precompiles/arch_test.go index 3c292a22d74..9f0cf186da7 100644 --- a/fvm/evm/precompiles/arch_test.go +++ b/fvm/evm/precompiles/arch_test.go @@ -3,9 +3,10 @@ package precompiles_test import ( "testing" + "github.com/stretchr/testify/require" + "github.com/onflow/flow-go/fvm/evm/precompiles" "github.com/onflow/flow-go/fvm/evm/testutils" - "github.com/stretchr/testify/require" ) func TestArchContract(t *testing.T) { diff --git a/fvm/evm/precompiles/precompile_test.go b/fvm/evm/precompiles/precompile_test.go index 05e71eb0576..21c3152ddb3 100644 --- a/fvm/evm/precompiles/precompile_test.go +++ b/fvm/evm/precompiles/precompile_test.go @@ -3,9 +3,10 @@ package precompiles_test import ( "testing" + "github.com/stretchr/testify/require" + "github.com/onflow/flow-go/fvm/evm/precompiles" "github.com/onflow/flow-go/fvm/evm/testutils" - "github.com/stretchr/testify/require" ) func TestMutiFunctionContract(t *testing.T) { From 9268f70c58253fda938768c6ec0ba8f74ea8b566 Mon Sep 17 00:00:00 2001 From: ramtinms Date: Tue, 16 Jan 2024 12:06:40 -0800 Subject: [PATCH 10/14] apply PR feedback --- fvm/evm/emulator/config.go | 4 +++- fvm/evm/emulator/emulator.go | 13 +++++-------- fvm/evm/handler/addressAllocator.go | 19 ++++++++++++------- fvm/evm/precompiles/precompile.go | 16 +++++++++++----- fvm/evm/precompiles/signature.go | 10 ++++++---- fvm/evm/precompiles/signature_test.go | 2 +- fvm/evm/types/emulator.go | 1 - 7 files changed, 38 insertions(+), 27 deletions(-) diff --git a/fvm/evm/emulator/config.go b/fvm/evm/emulator/config.go index 5593ae80d41..e1bfc0b1375 100644 --- a/fvm/evm/emulator/config.go +++ b/fvm/evm/emulator/config.go @@ -93,7 +93,6 @@ func defaultConfig() *Config { return gethCommon.BytesToHash(gethCrypto.Keccak256([]byte(new(big.Int).SetUint64(n).String()))) }, }, - ExtraPrecompiles: make(map[gethCommon.Address]gethVM.PrecompiledContract), } } @@ -193,6 +192,9 @@ func WithDirectCallBaseGasUsage(gas uint64) Option { func WithExtraPrecompiles(precompiles []types.Precompile) Option { return func(c *Config) *Config { for _, pc := range precompiles { + if c.ExtraPrecompiles == nil { + c.ExtraPrecompiles = make(map[gethCommon.Address]gethVM.PrecompiledContract) + } c.ExtraPrecompiles[pc.Address().ToCommon()] = pc } return c diff --git a/fvm/evm/emulator/emulator.go b/fvm/evm/emulator/emulator.go index 287839134a9..93561565a59 100644 --- a/fvm/evm/emulator/emulator.go +++ b/fvm/evm/emulator/emulator.go @@ -54,10 +54,7 @@ func (em *Emulator) NewReadOnlyBlockView(ctx types.BlockContext) (types.ReadOnly // NewBlockView constructs a new block view (mutable) func (em *Emulator) NewBlockView(ctx types.BlockContext) (types.BlockView, error) { cfg := newConfig(ctx) - err := SetupPrecompile(cfg) - if err != nil { - return nil, err - } + SetupPrecompile(cfg) return &BlockView{ config: cfg, rootAddr: em.rootAddr, @@ -285,7 +282,7 @@ func (proc *procedure) run(msg *gethCore.Message, txType uint8) (*types.Result, return &res, err } -func SetupPrecompile(cfg *Config) error { +func SetupPrecompile(cfg *Config) { rules := cfg.ChainRules() // captures the pointer to the map that has to be augmented var precompiles map[gethCommon.Address]gethVM.PrecompiledContract @@ -301,8 +298,8 @@ func SetupPrecompile(cfg *Config) error { default: precompiles = gethVM.PrecompiledContractsHomestead } - for k, v := range cfg.ExtraPrecompiles { - precompiles[k] = v + for addr, contract := range cfg.ExtraPrecompiles { + // we override if exist since we call this method on every block + precompiles[addr] = contract } - return nil } diff --git a/fvm/evm/handler/addressAllocator.go b/fvm/evm/handler/addressAllocator.go index a089e91803e..27f9349d0c4 100644 --- a/fvm/evm/handler/addressAllocator.go +++ b/fvm/evm/handler/addressAllocator.go @@ -9,14 +9,17 @@ import ( "github.com/onflow/flow-go/model/flow" ) -const ledgerAddressAllocatorKey = "AddressAllocator" +const ( + ledgerAddressAllocatorKey = "AddressAllocator" + uint64ByteSize = 8 +) var ( // prefixes: // the first 12 bytes of addresses allocation // leading zeros helps with storage and all zero is reserved for the EVM precompiles - FlowEVMPrecompileAddressPrefix = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} - FlowEVMCOAAddressPrefix = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2} + FlowEVMPrecompileAddressPrefix = [...]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} + FlowEVMCOAAddressPrefix = [...]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2} ) type AddressAllocator struct { @@ -61,8 +64,9 @@ func (aa *AddressAllocator) AllocateCOAAddress() (types.Address, error) { func MakeCOAAddress(index uint64) types.Address { var addr types.Address - copy(addr[:types.AddressLength-8], FlowEVMCOAAddressPrefix) - binary.BigEndian.PutUint64(addr[types.AddressLength-8:], index) + prefixIndex := types.AddressLength - uint64ByteSize + copy(addr[:prefixIndex], FlowEVMCOAAddressPrefix[:]) + binary.BigEndian.PutUint64(addr[prefixIndex:], index) return addr } @@ -73,7 +77,8 @@ func (aa *AddressAllocator) AllocatePrecompileAddress(index uint64) types.Addres func MakePrecompileAddress(index uint64) types.Address { var addr types.Address - copy(addr[:types.AddressLength-8], FlowEVMPrecompileAddressPrefix) - binary.BigEndian.PutUint64(addr[types.AddressLength-8:], index) + prefixIndex := types.AddressLength - uint64ByteSize + copy(addr[:prefixIndex], FlowEVMPrecompileAddressPrefix[:]) + binary.BigEndian.PutUint64(addr[prefixIndex:], index) return addr } diff --git a/fvm/evm/precompiles/precompile.go b/fvm/evm/precompiles/precompile.go index dcd7dab3822..160a4039e5b 100644 --- a/fvm/evm/precompiles/precompile.go +++ b/fvm/evm/precompiles/precompile.go @@ -1,11 +1,17 @@ package precompiles import ( - "fmt" + "errors" "github.com/onflow/flow-go/fvm/evm/types" ) +// InvalidMethodCallGasUsage captures how much gas we charge for invalid method call +const InvalidMethodCallGasUsage = uint64(1) + +// ErrInvalidMethodCall is returned when the method is not available on the contract +var ErrInvalidMethodCall = errors.New("invalid method call") + // Function is an interface for a function in a multi-function precompile contract type Function interface { // FunctionSignature returns the function signature for this function @@ -45,12 +51,12 @@ func (p *precompile) Address() types.Address { // RequiredPrice calculates the contract gas use func (p *precompile) RequiredGas(input []byte) uint64 { if len(input) < 4 { - return 0 + return InvalidMethodCallGasUsage } sig, data := SplitFunctionSignature(input) callable, found := p.functions[sig] if !found { - return 0 + return InvalidMethodCallGasUsage } return callable.ComputeGas(data) } @@ -58,12 +64,12 @@ func (p *precompile) RequiredGas(input []byte) uint64 { // Run runs the precompiled contract func (p *precompile) Run(input []byte) ([]byte, error) { if len(input) < 4 { - return nil, fmt.Errorf("invalid method") // TODO return the right error based on geth + return nil, ErrInvalidMethodCall } sig, data := SplitFunctionSignature(input) callable, found := p.functions[sig] if !found { - return nil, fmt.Errorf("invalid method") // TODO return the right error based on geth + return nil, ErrInvalidMethodCall } return callable.Run(data) } diff --git a/fvm/evm/precompiles/signature.go b/fvm/evm/precompiles/signature.go index b37184d057b..c178019bcca 100644 --- a/fvm/evm/precompiles/signature.go +++ b/fvm/evm/precompiles/signature.go @@ -7,8 +7,10 @@ import ( gethCrypto "github.com/ethereum/go-ethereum/crypto" ) +const FunctionSignatureLength = 4 + // This is derived as the first 4 bytes of the Keccak hash of the ASCII form of the signature of the method -type FunctionSignature [4]byte +type FunctionSignature [FunctionSignatureLength]byte func (fs FunctionSignature) Bytes() []byte { return fs[:] @@ -20,7 +22,7 @@ func (fs FunctionSignature) Bytes() []byte { func ComputeFunctionSignature(name string, args []string) FunctionSignature { var sig FunctionSignature input := fmt.Sprintf("%v(%v)", name, strings.Join(args, ",")) - copy(sig[0:4], gethCrypto.Keccak256([]byte(input))[:4]) + copy(sig[0:FunctionSignatureLength], gethCrypto.Keccak256([]byte(input))[:FunctionSignatureLength]) return sig } @@ -28,6 +30,6 @@ func ComputeFunctionSignature(name string, args []string) FunctionSignature { // returns the rest of the data func SplitFunctionSignature(input []byte) (FunctionSignature, []byte) { var funcSig FunctionSignature - copy(funcSig[:], input[0:4]) - return funcSig, input[4:] + copy(funcSig[:], input[0:FunctionSignatureLength]) + return funcSig, input[FunctionSignatureLength:] } diff --git a/fvm/evm/precompiles/signature_test.go b/fvm/evm/precompiles/signature_test.go index 3db898d182d..b46e4ee8dba 100644 --- a/fvm/evm/precompiles/signature_test.go +++ b/fvm/evm/precompiles/signature_test.go @@ -14,7 +14,7 @@ func TestFunctionSignatures(t *testing.T) { expected := gethCrypto.Keccak256([]byte("test()"))[:4] require.Equal(t, expected, precompiles.ComputeFunctionSignature("test", nil).Bytes()) - expected = gethCrypto.Keccak256([]byte("test(uint32,uint16)"))[:4] + expected = gethCrypto.Keccak256([]byte("test(uint32,uint16)"))[:precompiles.FunctionSignatureLength] require.Equal(t, expected, precompiles.ComputeFunctionSignature("test", []string{"uint32", "uint16"}).Bytes()) } diff --git a/fvm/evm/types/emulator.go b/fvm/evm/types/emulator.go index 750c241d2b8..01577a4132a 100644 --- a/fvm/evm/types/emulator.go +++ b/fvm/evm/types/emulator.go @@ -37,7 +37,6 @@ func NewDefaultBlockContext(BlockNumber uint64) BlockContext { BlockNumber: BlockNumber, DirectCallBaseGasUsage: DefaultDirectCallBaseGasUsage, DirectCallGasPrice: DefaultDirectCallGasPrice, - ExtraPrecompiles: make([]Precompile, 0), } } From d40d9f27f190ea5e7e98815b67f208eea92f853a Mon Sep 17 00:00:00 2001 From: ramtinms Date: Wed, 17 Jan 2024 10:46:33 -0800 Subject: [PATCH 11/14] apply PR feedback --- fvm/evm/evm.go | 1 - fvm/evm/precompiles/arch.go | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/fvm/evm/evm.go b/fvm/evm/evm.go index 5fbe6108a16..800130bc86e 100644 --- a/fvm/evm/evm.go +++ b/fvm/evm/evm.go @@ -42,7 +42,6 @@ func SetupEnvironment( return err } - // TODO replace with a proper contractHandler := handler.NewContractHandler(common.Address(flowToken), bs, aa, backend, em) stdlib.SetupEnvironment(env, contractHandler, service) diff --git a/fvm/evm/precompiles/arch.go b/fvm/evm/precompiles/arch.go index 057ac784b6e..1990346f413 100644 --- a/fvm/evm/precompiles/arch.go +++ b/fvm/evm/precompiles/arch.go @@ -48,5 +48,7 @@ func (c *flowBlockHeightFunction) Run(input []byte) ([]byte, error) { } encoded := make([]byte, 8) binary.BigEndian.PutUint64(encoded, bh) + // the EVM works natively in 256-bit words, + // we left pad to that size to prevent extra gas consumtion for masking. return gethCommon.LeftPadBytes(encoded, 32), nil } From c519905d72bd38d94d0e5e79b9bba1b5fc35c62c Mon Sep 17 00:00:00 2001 From: ramtinms Date: Mon, 22 Jan 2024 12:31:03 -0800 Subject: [PATCH 12/14] tidy testutils --- fvm/evm/testutils/emulator.go | 51 --------------------------- fvm/evm/testutils/misc.go | 65 +++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 51 deletions(-) create mode 100644 fvm/evm/testutils/misc.go diff --git a/fvm/evm/testutils/emulator.go b/fvm/evm/testutils/emulator.go index 5f7f2ce3068..0cdc0d4d93c 100644 --- a/fvm/evm/testutils/emulator.go +++ b/fvm/evm/testutils/emulator.go @@ -1,14 +1,9 @@ package testutils import ( - cryptoRand "crypto/rand" "math/big" - "math/rand" - "testing" - gethCommon "github.com/ethereum/go-ethereum/common" gethTypes "github.com/ethereum/go-ethereum/core/types" - "github.com/stretchr/testify/require" "github.com/onflow/flow-go/fvm/evm/types" ) @@ -72,49 +67,3 @@ func (em *TestEmulator) RunTransaction(tx *gethTypes.Transaction) (*types.Result } return em.RunTransactionFunc(tx) } - -func RandomCommonHash(t testing.TB) gethCommon.Hash { - ret := gethCommon.Hash{} - _, err := cryptoRand.Read(ret[:gethCommon.HashLength]) - require.NoError(t, err) - return ret -} - -func RandomBigInt(limit int64) *big.Int { - return big.NewInt(rand.Int63n(limit) + 1) -} - -func RandomAddress(t testing.TB) types.Address { - return types.NewAddress(RandomCommonAddress(t)) -} - -func RandomCommonAddress(t testing.TB) gethCommon.Address { - ret := gethCommon.Address{} - _, err := cryptoRand.Read(ret[:gethCommon.AddressLength]) - require.NoError(t, err) - return ret -} - -func RandomGas(limit int64) uint64 { - return uint64(rand.Int63n(limit) + 1) -} - -func RandomData(t testing.TB) []byte { - // byte size [1, 100] - size := rand.Intn(100) + 1 - ret := make([]byte, size) - _, err := cryptoRand.Read(ret[:]) - require.NoError(t, err) - return ret -} - -func GetRandomLogFixture(t testing.TB) *gethTypes.Log { - return &gethTypes.Log{ - Address: RandomCommonAddress(t), - Topics: []gethCommon.Hash{ - RandomCommonHash(t), - RandomCommonHash(t), - }, - Data: RandomData(t), - } -} diff --git a/fvm/evm/testutils/misc.go b/fvm/evm/testutils/misc.go new file mode 100644 index 00000000000..b335ad3adfb --- /dev/null +++ b/fvm/evm/testutils/misc.go @@ -0,0 +1,65 @@ +package testutils + +import ( + cryptoRand "crypto/rand" + "math/big" + "math/rand" + "testing" + + gethCommon "github.com/ethereum/go-ethereum/common" + gethTypes "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/require" + + "github.com/onflow/flow-go/fvm/evm/types" +) + +func RandomCommonHash(t testing.TB) gethCommon.Hash { + ret := gethCommon.Hash{} + _, err := cryptoRand.Read(ret[:gethCommon.HashLength]) + require.NoError(t, err) + return ret +} + +func RandomBigInt(limit int64) *big.Int { + return big.NewInt(rand.Int63n(limit) + 1) +} + +func RandomAddress(t testing.TB) types.Address { + return types.NewAddress(RandomCommonAddress(t)) +} + +func RandomCommonAddress(t testing.TB) gethCommon.Address { + ret := gethCommon.Address{} + _, err := cryptoRand.Read(ret[:gethCommon.AddressLength]) + require.NoError(t, err) + return ret +} + +func RandomGas(limit int64) uint64 { + return uint64(rand.Int63n(limit) + 1) +} + +func RandomData(t testing.TB) []byte { + // byte size [1, 100] + size := rand.Intn(100) + 1 + ret := make([]byte, size) + _, err := cryptoRand.Read(ret[:]) + require.NoError(t, err) + return ret +} + +func GetRandomLogFixture(t testing.TB) *gethTypes.Log { + return &gethTypes.Log{ + Address: RandomCommonAddress(t), + Topics: []gethCommon.Hash{ + RandomCommonHash(t), + RandomCommonHash(t), + }, + Data: RandomData(t), + } +} + +// MakeABalanceInFlow makes a balance object that has `amount` Flow Token in it +func MakeABalanceInFlow(amount uint64) types.Balance { + return types.Balance(uint64(100_000_000) * amount) +} From a6abd9b9ae7efe8c96f8b3ae11a11004bfefa3c7 Mon Sep 17 00:00:00 2001 From: ramtinms Date: Mon, 22 Jan 2024 12:44:30 -0800 Subject: [PATCH 13/14] apply pr feedbacks --- fvm/evm/handler/addressAllocator.go | 12 ++++++------ fvm/evm/handler/handler_test.go | 8 ++------ fvm/evm/precompiles/arch.go | 6 ++++-- fvm/evm/precompiles/precompile.go | 16 ++++++++-------- fvm/evm/precompiles/precompile_test.go | 18 +++++++++--------- fvm/evm/precompiles/signature.go | 24 ++++++++++++------------ fvm/evm/precompiles/signature_test.go | 15 ++++++++++----- 7 files changed, 51 insertions(+), 48 deletions(-) diff --git a/fvm/evm/handler/addressAllocator.go b/fvm/evm/handler/addressAllocator.go index 27f9349d0c4..f504bb4e050 100644 --- a/fvm/evm/handler/addressAllocator.go +++ b/fvm/evm/handler/addressAllocator.go @@ -63,11 +63,7 @@ func (aa *AddressAllocator) AllocateCOAAddress() (types.Address, error) { } func MakeCOAAddress(index uint64) types.Address { - var addr types.Address - prefixIndex := types.AddressLength - uint64ByteSize - copy(addr[:prefixIndex], FlowEVMCOAAddressPrefix[:]) - binary.BigEndian.PutUint64(addr[prefixIndex:], index) - return addr + return makePrefixedAddress(index, FlowEVMCOAAddressPrefix) } func (aa *AddressAllocator) AllocatePrecompileAddress(index uint64) types.Address { @@ -76,9 +72,13 @@ func (aa *AddressAllocator) AllocatePrecompileAddress(index uint64) types.Addres } func MakePrecompileAddress(index uint64) types.Address { + return makePrefixedAddress(index, FlowEVMPrecompileAddressPrefix) +} + +func makePrefixedAddress(index uint64, prefix [12]byte) types.Address { var addr types.Address prefixIndex := types.AddressLength - uint64ByteSize - copy(addr[:prefixIndex], FlowEVMPrecompileAddressPrefix[:]) + copy(addr[:prefixIndex], prefix[:]) binary.BigEndian.PutUint64(addr[prefixIndex:], index) return addr } diff --git a/fvm/evm/handler/handler_test.go b/fvm/evm/handler/handler_test.go index 0463481aaef..2d374ef231f 100644 --- a/fvm/evm/handler/handler_test.go +++ b/fvm/evm/handler/handler_test.go @@ -477,9 +477,7 @@ func TestHandler_BridgedAccount(t *testing.T) { require.NotNil(t, foa) // deposit 10000 flow - orgBalance, err := types.NewBalanceFromAttoFlow(new(big.Int).Mul(big.NewInt(1e18), big.NewInt(10000))) - require.NoError(t, err) - vault := types.NewFlowTokenVault(orgBalance) + vault := types.NewFlowTokenVault(testutils.MakeABalanceInFlow(10000)) foa.Deposit(vault) testContract := testutils.GetStorageTestContract(t) @@ -519,9 +517,7 @@ func TestHandler_BridgedAccount(t *testing.T) { foa := h.AccountByAddress(h.AllocateAddress(), true) require.NotNil(t, foa) - orgBalance, err := types.NewBalanceFromAttoFlow(new(big.Int).Mul(big.NewInt(1e18), big.NewInt(10000))) - require.NoError(t, err) - vault := types.NewFlowTokenVault(orgBalance) + vault := types.NewFlowTokenVault(testutils.MakeABalanceInFlow(10000)) foa.Deposit(vault) arch := handler.MakePrecompileAddress(1) diff --git a/fvm/evm/precompiles/arch.go b/fvm/evm/precompiles/arch.go index 1990346f413..ca6477e311a 100644 --- a/fvm/evm/precompiles/arch.go +++ b/fvm/evm/precompiles/arch.go @@ -10,12 +10,14 @@ import ( ) var ( - FlowBlockHeightFuncSig = ComputeFunctionSignature("flowBlockHeight", nil) + FlowBlockHeightFuncSig = ComputeFunctionSelector("flowBlockHeight", nil) // TODO update me with a higher value if needed FlowBlockHeightFixedGas = uint64(1) ) // ArchContract return a procompile for the Cadence Arch contract +// which facilitates access of Flow EVM environment into the Cadence environment. +// for more details see this Flip 223. func ArchContract( address types.Address, heightProvider func() (uint64, error), @@ -30,7 +32,7 @@ type flowBlockHeightFunction struct { flowBlockHeightLookUp func() (uint64, error) } -func (c *flowBlockHeightFunction) FunctionSignature() FunctionSignature { +func (c *flowBlockHeightFunction) FunctionSelector() FunctionSelector { return FlowBlockHeightFuncSig } diff --git a/fvm/evm/precompiles/precompile.go b/fvm/evm/precompiles/precompile.go index 160a4039e5b..7deb5888754 100644 --- a/fvm/evm/precompiles/precompile.go +++ b/fvm/evm/precompiles/precompile.go @@ -14,8 +14,8 @@ var ErrInvalidMethodCall = errors.New("invalid method call") // Function is an interface for a function in a multi-function precompile contract type Function interface { - // FunctionSignature returns the function signature for this function - FunctionSignature() FunctionSignature + // FunctionSelector returns the function selector bytes for this function + FunctionSelector() FunctionSelector // ComputeGas computes the gas needed for the given input ComputeGas(input []byte) uint64 @@ -30,30 +30,30 @@ func MultiFunctionPrecompileContract( functions []Function, ) types.Precompile { pc := &precompile{ - functions: make(map[FunctionSignature]Function), + functions: make(map[FunctionSelector]Function), address: address, } for _, f := range functions { - pc.functions[f.FunctionSignature()] = f + pc.functions[f.FunctionSelector()] = f } return pc } type precompile struct { address types.Address - functions map[FunctionSignature]Function + functions map[FunctionSelector]Function } func (p *precompile) Address() types.Address { return p.address } -// RequiredPrice calculates the contract gas use +// RequiredGas calculates the contract gas use func (p *precompile) RequiredGas(input []byte) uint64 { if len(input) < 4 { return InvalidMethodCallGasUsage } - sig, data := SplitFunctionSignature(input) + sig, data := SplitFunctionSelector(input) callable, found := p.functions[sig] if !found { return InvalidMethodCallGasUsage @@ -66,7 +66,7 @@ func (p *precompile) Run(input []byte) ([]byte, error) { if len(input) < 4 { return nil, ErrInvalidMethodCall } - sig, data := SplitFunctionSignature(input) + sig, data := SplitFunctionSelector(input) callable, found := p.functions[sig] if !found { return nil, ErrInvalidMethodCall diff --git a/fvm/evm/precompiles/precompile_test.go b/fvm/evm/precompiles/precompile_test.go index 21c3152ddb3..e8c599b0130 100644 --- a/fvm/evm/precompiles/precompile_test.go +++ b/fvm/evm/precompiles/precompile_test.go @@ -11,7 +11,7 @@ import ( func TestMutiFunctionContract(t *testing.T) { address := testutils.RandomAddress(t) - sig := precompiles.FunctionSignature{1, 2, 3, 4} + sig := precompiles.FunctionSelector{1, 2, 3, 4} data := "data" input := append(sig[:], data...) gas := uint64(20) @@ -19,7 +19,7 @@ func TestMutiFunctionContract(t *testing.T) { pc := precompiles.MultiFunctionPrecompileContract(address, []precompiles.Function{ &mockedFunction{ - FunctionSignatureFunc: func() precompiles.FunctionSignature { + FunctionSelectorFunc: func() precompiles.FunctionSelector { return sig }, ComputeGasFunc: func(inp []byte) uint64 { @@ -40,20 +40,20 @@ func TestMutiFunctionContract(t *testing.T) { input2 := []byte("non existing signature and data") _, err = pc.Run(input2) - require.Error(t, err) + require.Equal(t, precompiles.ErrInvalidMethodCall, err) } type mockedFunction struct { - FunctionSignatureFunc func() precompiles.FunctionSignature - ComputeGasFunc func(input []byte) uint64 - RunFunc func(input []byte) ([]byte, error) + FunctionSelectorFunc func() precompiles.FunctionSelector + ComputeGasFunc func(input []byte) uint64 + RunFunc func(input []byte) ([]byte, error) } -func (mf *mockedFunction) FunctionSignature() precompiles.FunctionSignature { - if mf.FunctionSignatureFunc == nil { +func (mf *mockedFunction) FunctionSelector() precompiles.FunctionSelector { + if mf.FunctionSelectorFunc == nil { panic("method not set for mocked function") } - return mf.FunctionSignatureFunc() + return mf.FunctionSelectorFunc() } func (mf *mockedFunction) ComputeGas(input []byte) uint64 { diff --git a/fvm/evm/precompiles/signature.go b/fvm/evm/precompiles/signature.go index c178019bcca..a62c8f5b9ac 100644 --- a/fvm/evm/precompiles/signature.go +++ b/fvm/evm/precompiles/signature.go @@ -7,29 +7,29 @@ import ( gethCrypto "github.com/ethereum/go-ethereum/crypto" ) -const FunctionSignatureLength = 4 +const FunctionSelectorLength = 4 // This is derived as the first 4 bytes of the Keccak hash of the ASCII form of the signature of the method -type FunctionSignature [FunctionSignatureLength]byte +type FunctionSelector [FunctionSelectorLength]byte -func (fs FunctionSignature) Bytes() []byte { +func (fs FunctionSelector) Bytes() []byte { return fs[:] } -// ComputeFunctionSignature computes the function signture +// ComputeFunctionSelector computes the function selector // given the canonical name of function and args. // for example the canonical format for int is int256 -func ComputeFunctionSignature(name string, args []string) FunctionSignature { - var sig FunctionSignature +func ComputeFunctionSelector(name string, args []string) FunctionSelector { + var sig FunctionSelector input := fmt.Sprintf("%v(%v)", name, strings.Join(args, ",")) - copy(sig[0:FunctionSignatureLength], gethCrypto.Keccak256([]byte(input))[:FunctionSignatureLength]) + copy(sig[0:FunctionSelectorLength], gethCrypto.Keccak256([]byte(input))[:FunctionSelectorLength]) return sig } -// SplitFunctionSignature splits the function signature from input data and +// SplitFunctionSelector splits the function signature from input data and // returns the rest of the data -func SplitFunctionSignature(input []byte) (FunctionSignature, []byte) { - var funcSig FunctionSignature - copy(funcSig[:], input[0:FunctionSignatureLength]) - return funcSig, input[FunctionSignatureLength:] +func SplitFunctionSelector(input []byte) (FunctionSelector, []byte) { + var funcSig FunctionSelector + copy(funcSig[:], input[0:FunctionSelectorLength]) + return funcSig, input[FunctionSelectorLength:] } diff --git a/fvm/evm/precompiles/signature_test.go b/fvm/evm/precompiles/signature_test.go index b46e4ee8dba..9adecc5b755 100644 --- a/fvm/evm/precompiles/signature_test.go +++ b/fvm/evm/precompiles/signature_test.go @@ -9,12 +9,17 @@ import ( "github.com/onflow/flow-go/fvm/evm/precompiles" ) -func TestFunctionSignatures(t *testing.T) { - +func TestFunctionSelector(t *testing.T) { expected := gethCrypto.Keccak256([]byte("test()"))[:4] - require.Equal(t, expected, precompiles.ComputeFunctionSignature("test", nil).Bytes()) + require.Equal(t, expected, precompiles.ComputeFunctionSelector("test", nil).Bytes()) - expected = gethCrypto.Keccak256([]byte("test(uint32,uint16)"))[:precompiles.FunctionSignatureLength] + expected = gethCrypto.Keccak256([]byte("test(uint32,uint16)"))[:precompiles.FunctionSelectorLength] require.Equal(t, expected, - precompiles.ComputeFunctionSignature("test", []string{"uint32", "uint16"}).Bytes()) + precompiles.ComputeFunctionSelector("test", []string{"uint32", "uint16"}).Bytes()) + + selector := []byte{1, 2, 3, 4} + data := []byte{5, 6, 7, 8} + retSelector, retData := precompiles.SplitFunctionSelector(append(selector, data...)) + require.Equal(t, selector, retSelector[:]) + require.Equal(t, data, retData) } From a930a0029766f5854b88a27d7c191e6b7531c677 Mon Sep 17 00:00:00 2001 From: ramtinms Date: Tue, 23 Jan 2024 01:19:00 -0800 Subject: [PATCH 14/14] apply PR feedback --- fvm/evm/handler/addressAllocator.go | 7 ++++--- fvm/evm/precompiles/precompile.go | 4 ++-- fvm/evm/precompiles/precompile_test.go | 2 ++ fvm/evm/precompiles/signature_test.go | 2 ++ 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/fvm/evm/handler/addressAllocator.go b/fvm/evm/handler/addressAllocator.go index f504bb4e050..d1dc8299130 100644 --- a/fvm/evm/handler/addressAllocator.go +++ b/fvm/evm/handler/addressAllocator.go @@ -12,14 +12,15 @@ import ( const ( ledgerAddressAllocatorKey = "AddressAllocator" uint64ByteSize = 8 + addressPrefixLen = 12 ) var ( // prefixes: // the first 12 bytes of addresses allocation // leading zeros helps with storage and all zero is reserved for the EVM precompiles - FlowEVMPrecompileAddressPrefix = [...]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} - FlowEVMCOAAddressPrefix = [...]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2} + FlowEVMPrecompileAddressPrefix = [addressPrefixLen]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} + FlowEVMCOAAddressPrefix = [addressPrefixLen]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2} ) type AddressAllocator struct { @@ -75,7 +76,7 @@ func MakePrecompileAddress(index uint64) types.Address { return makePrefixedAddress(index, FlowEVMPrecompileAddressPrefix) } -func makePrefixedAddress(index uint64, prefix [12]byte) types.Address { +func makePrefixedAddress(index uint64, prefix [addressPrefixLen]byte) types.Address { var addr types.Address prefixIndex := types.AddressLength - uint64ByteSize copy(addr[:prefixIndex], prefix[:]) diff --git a/fvm/evm/precompiles/precompile.go b/fvm/evm/precompiles/precompile.go index 7deb5888754..85f96cd59c1 100644 --- a/fvm/evm/precompiles/precompile.go +++ b/fvm/evm/precompiles/precompile.go @@ -50,7 +50,7 @@ func (p *precompile) Address() types.Address { // RequiredGas calculates the contract gas use func (p *precompile) RequiredGas(input []byte) uint64 { - if len(input) < 4 { + if len(input) < FunctionSelectorLength { return InvalidMethodCallGasUsage } sig, data := SplitFunctionSelector(input) @@ -63,7 +63,7 @@ func (p *precompile) RequiredGas(input []byte) uint64 { // Run runs the precompiled contract func (p *precompile) Run(input []byte) ([]byte, error) { - if len(input) < 4 { + if len(input) < FunctionSelectorLength { return nil, ErrInvalidMethodCall } sig, data := SplitFunctionSelector(input) diff --git a/fvm/evm/precompiles/precompile_test.go b/fvm/evm/precompiles/precompile_test.go index e8c599b0130..654cc71c14f 100644 --- a/fvm/evm/precompiles/precompile_test.go +++ b/fvm/evm/precompiles/precompile_test.go @@ -10,6 +10,8 @@ import ( ) func TestMutiFunctionContract(t *testing.T) { + t.Parallel() + address := testutils.RandomAddress(t) sig := precompiles.FunctionSelector{1, 2, 3, 4} data := "data" diff --git a/fvm/evm/precompiles/signature_test.go b/fvm/evm/precompiles/signature_test.go index 9adecc5b755..d6f36b9fffe 100644 --- a/fvm/evm/precompiles/signature_test.go +++ b/fvm/evm/precompiles/signature_test.go @@ -10,6 +10,8 @@ import ( ) func TestFunctionSelector(t *testing.T) { + t.Parallel() + expected := gethCrypto.Keccak256([]byte("test()"))[:4] require.Equal(t, expected, precompiles.ComputeFunctionSelector("test", nil).Bytes())