Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[Flow EVM] extend EVM precompiles with cadence arch #5233

Merged
merged 23 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 42 additions & 20 deletions fvm/evm/emulator/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import (
"math"
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
gethCommon "github.com/ethereum/go-ethereum/common"
ramtinms marked this conversation as resolved.
Show resolved Hide resolved
gethCore "github.com/ethereum/go-ethereum/core"
gethVM "github.com/ethereum/go-ethereum/core/vm"
gethCrypto "github.com/ethereum/go-ethereum/crypto"
gethParams "github.com/ethereum/go-ethereum/params"

"github.com/onflow/flow-go/fvm/evm/types"
)

var (
Expand All @@ -21,15 +23,24 @@ 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() gethParams.Rules {
return c.ChainConfig.Rules(
c.BlockContext.BlockNumber,
c.BlockContext.Random != nil,
c.BlockContext.Time)
}

// DefaultChainConfig is the default chain config which
Expand All @@ -39,7 +50,7 @@ type Config struct {
// 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 = &params.ChainConfig{
var DefaultChainConfig = &gethParams.ChainConfig{
ChainID: FlowEVMTestnetChainID, // default is testnet

// Fork scheduling based on block heights
Expand All @@ -66,22 +77,23 @@ var DefaultChainConfig = &params.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),
}
}

Expand Down Expand Up @@ -114,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
Expand All @@ -138,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
Expand All @@ -162,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
Expand All @@ -176,3 +188,13 @@ func WithDirectCallBaseGasUsage(gas uint64) Option {
return c
}
}

// WithExtraPrecompiles appends precompile list with extra precompiles
func WithExtraPrecompiles(precompiles []types.Precompile) Option {
return func(c *Config) *Config {
for _, pc := range precompiles {
c.ExtraPrecompiles[pc.Address().ToCommon()] = pc
}
return c
}
}
27 changes: 27 additions & 0 deletions fvm/evm/emulator/emulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ func newConfig(ctx types.BlockContext) *Config {
WithBlockNumber(new(big.Int).SetUint64(ctx.BlockNumber)),
WithCoinbase(ctx.GasFeeCollector.ToCommon()),
WithDirectCallBaseGasUsage(ctx.DirectCallBaseGasUsage),
WithExtraPrecompiles(ctx.ExtraPrecompiles),
)
}

Expand All @@ -53,6 +54,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,
Expand Down Expand Up @@ -279,3 +284,25 @@ func (proc *procedure) run(msg *gethCore.Message, txType uint8) (*types.Result,
}
return &res, err
}

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:
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 {
precompiles[k] = v
ramtinms marked this conversation as resolved.
Show resolved Hide resolved
}
return nil
}
78 changes: 77 additions & 1 deletion fvm/evm/emulator/emulator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -351,3 +350,80 @@ 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}
addr := testutils.RandomAddress(t)
pc := &MockedPrecompile{
AddressFunc: func() types.Address {
return addr
},
RequiredGasFunc: func(input []byte) uint64 {
return uint64(10)
},
RunFunc: func(inp []byte) ([]byte, error) {
require.Equal(t, input, inp)
return output, nil
},
}

ctx := types.NewDefaultBlockContext(blockNumber.Uint64())
ctx.ExtraPrecompiles = []types.Precompile{pc}

blk, err := em.NewBlockView(ctx)
require.NoError(t, err)

res, err := blk.DirectCall(
types.NewContractCall(
testAccount,
types.NewAddress(addr.ToCommon()),
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 {
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")
}
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)
}
1 change: 1 addition & 0 deletions fvm/evm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 0 additions & 3 deletions fvm/evm/evm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
36 changes: 30 additions & 6 deletions fvm/evm/handler/addressAllocator.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ import (

const ledgerAddressAllocatorKey = "AddressAllocator"

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}
ramtinms marked this conversation as resolved.
Show resolved Hide resolved
)

type AddressAllocator struct {
led atree.Ledger
flexAddress flow.Address
Expand All @@ -26,8 +34,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
Expand All @@ -38,10 +46,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)
Expand All @@ -53,3 +58,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-8], FlowEVMCOAAddressPrefix)
ramtinms marked this conversation as resolved.
Show resolved Hide resolved
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-8], FlowEVMPrecompileAddressPrefix)
binary.BigEndian.PutUint64(addr[types.AddressLength-8:], index)
return addr
}
12 changes: 8 additions & 4 deletions fvm/evm/handler/addressAllocator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,20 @@ func TestAddressAllocator(t *testing.T) {
aa, err := handler.NewAddressAllocator(backend, root)
require.NoError(t, err)

adr := aa.AllocatePrecompileAddress(3)
expectedAddress := types.NewAddress(gethCommon.HexToAddress("0x0000000000000000000000010000000000000003"))
require.Equal(t, expectedAddress, adr)

// 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("0x0000000000000000000000020000000000000001"))
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("0x0000000000000000000000020000000000000002"))
require.Equal(t, expectedAddress, adr)
})

Expand Down
Loading
Loading