diff --git a/fvm/evm/handler/addressAllocator.go b/fvm/evm/handler/addressAllocator.go index 55a20527acf..316c2fa0d9f 100644 --- a/fvm/evm/handler/addressAllocator.go +++ b/fvm/evm/handler/addressAllocator.go @@ -12,15 +12,9 @@ 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 = [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} + // addressIndexShuffleSeed is used for shuffling address index + // shuffling index is used to make address postfixes look random + addressIndexShuffleSeed = uint64(0xFFEEDDCCBBAA9987) ) type AddressAllocator struct { @@ -43,32 +37,12 @@ func (aa *AddressAllocator) COAFactoryAddress() types.Address { } // 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 - } - // default value for uuid is 1 - uuid := uint64(1) - if len(data) > 0 { - uuid = binary.BigEndian.Uint64(data) - } - - target := MakeCOAAddress(uuid) - - // store new uuid - newData := make([]byte, 8) - binary.BigEndian.PutUint64(newData, uuid+1) - err = aa.led.SetValue(aa.flexAddress[:], []byte(ledgerAddressAllocatorKey), newData) - if err != nil { - return types.Address{}, err - } - - return target, nil +func (aa *AddressAllocator) AllocateCOAAddress(uuid uint64) types.Address { + return MakeCOAAddress(uuid) } func MakeCOAAddress(index uint64) types.Address { - return makePrefixedAddress(index, FlowEVMCOAAddressPrefix) + return makePrefixedAddress(shuffleAddressIndex(index), types.FlowEVMCOAAddressPrefix) } func (aa *AddressAllocator) AllocatePrecompileAddress(index uint64) types.Address { @@ -77,13 +51,20 @@ func (aa *AddressAllocator) AllocatePrecompileAddress(index uint64) types.Addres } func MakePrecompileAddress(index uint64) types.Address { - return makePrefixedAddress(index, FlowEVMPrecompileAddressPrefix) + return makePrefixedAddress(index, types.FlowEVMExtendedPrecompileAddressPrefix) } -func makePrefixedAddress(index uint64, prefix [addressPrefixLen]byte) types.Address { +func makePrefixedAddress( + index uint64, + prefix [types.FlowEVMSpecialAddressPrefixLen]byte, +) types.Address { var addr types.Address prefixIndex := types.AddressLength - uint64ByteSize copy(addr[:prefixIndex], prefix[:]) binary.BigEndian.PutUint64(addr[prefixIndex:], index) return addr } + +func shuffleAddressIndex(preShuffleIndex uint64) uint64 { + return uint64(preShuffleIndex * addressIndexShuffleSeed) +} diff --git a/fvm/evm/handler/addressAllocator_test.go b/fvm/evm/handler/addressAllocator_test.go index a2729ba4028..b2471da358c 100644 --- a/fvm/evm/handler/addressAllocator_test.go +++ b/fvm/evm/handler/addressAllocator_test.go @@ -21,18 +21,22 @@ func TestAddressAllocator(t *testing.T) { adr := aa.AllocatePrecompileAddress(3) expectedAddress := types.NewAddress(gethCommon.HexToAddress("0x0000000000000000000000010000000000000003")) require.Equal(t, expectedAddress, adr) + // check conforming to types + require.False(t, types.IsACOAAddress(adr)) // test default value fall back - adr, err = aa.AllocateCOAAddress() - require.NoError(t, err) - expectedAddress = types.NewAddress(gethCommon.HexToAddress("0x0000000000000000000000020000000000000001")) + adr = aa.AllocateCOAAddress(1) + expectedAddress = types.NewAddress(gethCommon.HexToAddress("0x000000000000000000000002ffeeddccbbaa9987")) require.Equal(t, expectedAddress, adr) + // check conforming to types + require.True(t, types.IsACOAAddress(adr)) // continous allocation logic - adr, err = aa.AllocateCOAAddress() - require.NoError(t, err) - expectedAddress = types.NewAddress(gethCommon.HexToAddress("0x0000000000000000000000020000000000000002")) + adr = aa.AllocateCOAAddress(2) + expectedAddress = types.NewAddress(gethCommon.HexToAddress("0x000000000000000000000002ffddbb997755330e")) require.Equal(t, expectedAddress, adr) + // check conforming to types + require.True(t, types.IsACOAAddress(adr)) // factory factory := aa.COAFactoryAddress() diff --git a/fvm/evm/handler/handler.go b/fvm/evm/handler/handler.go index f055e2914c4..c5805004ff1 100644 --- a/fvm/evm/handler/handler.go +++ b/fvm/evm/handler/handler.go @@ -62,10 +62,9 @@ func getPrecompiles( } // DeployCOA deploys a cadence-owned-account and returns the address -func (h *ContractHandler) DeployCOA() types.Address { - target, err := h.addressAllocator.AllocateCOAAddress() +func (h *ContractHandler) DeployCOA(uuid uint64) types.Address { + target := h.addressAllocator.AllocateCOAAddress(uuid) gaslimit := types.GasLimit(COAContractDeploymentRequiredGas) - handleError(err) h.checkGasLimit(gaslimit) factory := h.addressAllocator.COAFactoryAddress() diff --git a/fvm/evm/handler/handler_benchmark_test.go b/fvm/evm/handler/handler_benchmark_test.go index a44108b620b..8c7ff388706 100644 --- a/fvm/evm/handler/handler_benchmark_test.go +++ b/fvm/evm/handler/handler_benchmark_test.go @@ -26,7 +26,7 @@ func benchmarkStorageGrowth(b *testing.B, accountCount, setupKittyCount int) { // setup several of accounts // note that trie growth is the function of number of accounts for i := 0; i < accountCount; i++ { - account := handler.AccountByAddress(handler.DeployCOA(), true) + account := handler.AccountByAddress(handler.DeployCOA(uint64(i+1)), true) account.Deposit(types.NewFlowTokenVault(types.NewBalanceFromUFix64(100))) accounts[i] = account } diff --git a/fvm/evm/handler/handler_test.go b/fvm/evm/handler/handler_test.go index de71fa4f460..2cef07199e8 100644 --- a/fvm/evm/handler/handler_test.go +++ b/fvm/evm/handler/handler_test.go @@ -198,7 +198,7 @@ func TestHandler_TransactionRun(t *testing.T) { eoa := testutils.GetTestEOAAccount(t, testutils.EOATestAccount1KeyHex) // deposit 1 Flow to the foa account - addr := handler.DeployCOA() + addr := handler.DeployCOA(1) orgBalance := types.NewBalanceFromUFix64(types.OneFlowInUFix64) vault := types.NewFlowTokenVault(orgBalance) foa := handler.AccountByAddress(addr, true) @@ -224,7 +224,7 @@ func TestHandler_TransactionRun(t *testing.T) { ) // setup coinbase - foa2 := handler.DeployCOA() + foa2 := handler.DeployCOA(2) account2 := handler.AccountByAddress(foa2, true) require.Equal(t, types.NewBalanceFromUFix64(0), account2.Balance()) @@ -278,7 +278,7 @@ func TestHandler_COA(t *testing.T) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { handler := SetupHandler(t, backend, rootAddr) - foa := handler.AccountByAddress(handler.DeployCOA(), true) + foa := handler.AccountByAddress(handler.DeployCOA(1), true) require.NotNil(t, foa) zeroBalance := types.NewBalance(big.NewInt(0)) @@ -342,12 +342,12 @@ func TestHandler_COA(t *testing.T) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { h := SetupHandler(t, backend, rootAddr) - coa := h.DeployCOA() + coa := h.DeployCOA(1) acc := h.AccountByAddress(coa, true) require.NotEmpty(t, acc.Code()) // make a second account with some money - coa2 := h.DeployCOA() + coa2 := h.DeployCOA(2) acc2 := h.AccountByAddress(coa2, true) acc2.Deposit(types.NewFlowTokenVault(types.MakeABalanceInFlow(100))) @@ -501,7 +501,7 @@ func TestHandler_COA(t *testing.T) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { handler := SetupHandler(t, backend, rootAddr) - foa := handler.AccountByAddress(handler.DeployCOA(), true) + foa := handler.AccountByAddress(handler.DeployCOA(1), true) require.NotNil(t, foa) // deposit 10000 flow @@ -543,7 +543,7 @@ func TestHandler_COA(t *testing.T) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { h := SetupHandler(t, backend, rootAddr) - foa := h.AccountByAddress(h.DeployCOA(), true) + foa := h.AccountByAddress(h.DeployCOA(1), true) require.NotNil(t, foa) vault := types.NewFlowTokenVault(types.MakeABalanceInFlow(10000)) @@ -569,7 +569,7 @@ func TestHandler_COA(t *testing.T) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { handler := SetupHandler(t, backend, rootAddr) - foa := handler.AccountByAddress(handler.DeployCOA(), true) + foa := handler.AccountByAddress(handler.DeployCOA(1), true) require.NotNil(t, foa) vault := types.NewFlowTokenVault(types.MakeABalanceInFlow(100)) diff --git a/fvm/evm/stdlib/contract.cdc b/fvm/evm/stdlib/contract.cdc index 3151542131b..bc902b26bdc 100644 --- a/fvm/evm/stdlib/contract.cdc +++ b/fvm/evm/stdlib/contract.cdc @@ -50,9 +50,14 @@ contract EVM { resource BridgedAccount { access(self) - let addressBytes: [UInt8; 20] + var addressBytes: [UInt8; 20] - init(addressBytes: [UInt8; 20]) { + init() { + self.addressBytes = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + } + + access(contract) + fun setAddress(addressBytes: [UInt8; 20]) { self.addressBytes = addressBytes } @@ -127,9 +132,10 @@ contract EVM { /// Creates a new bridged account access(all) fun createBridgedAccount(): @BridgedAccount { - return <-create BridgedAccount( - addressBytes: InternalEVM.createBridgedAccount() - ) + let acc <-create BridgedAccount() + let addr = InternalEVM.createBridgedAccount(uuid: acc.uuid) + acc.setAddress(addressBytes: addr) + return <-acc } /// Runs an a RLP-encoded EVM transaction, deducts the gas fees, diff --git a/fvm/evm/stdlib/contract.go b/fvm/evm/stdlib/contract.go index 64b17d1d08f..9fc790937da 100644 --- a/fvm/evm/stdlib/contract.go +++ b/fvm/evm/stdlib/contract.go @@ -1140,6 +1140,12 @@ func newInternalEVMTypeCallFunction( const internalEVMTypeCreateBridgedAccountFunctionName = "createBridgedAccount" var internalEVMTypeCreateBridgedAccountFunctionType = &sema.FunctionType{ + Parameters: []sema.Parameter{ + { + Label: "uuid", + TypeAnnotation: sema.NewTypeAnnotation(sema.UInt64Type), + }, + }, ReturnTypeAnnotation: sema.NewTypeAnnotation(evmAddressBytesType), } @@ -1152,7 +1158,11 @@ func newInternalEVMTypeCreateBridgedAccountFunction( internalEVMTypeCreateBridgedAccountFunctionType, func(invocation interpreter.Invocation) interpreter.Value { inter := invocation.Interpreter - address := handler.DeployCOA() + uuid, ok := invocation.Arguments[0].(interpreter.UInt64Value) + if !ok { + panic(errors.NewUnreachableError()) + } + address := handler.DeployCOA(uint64(uuid)) return EVMAddressToAddressBytesArrayValue(inter, address) }, ) diff --git a/fvm/evm/stdlib/contract_test.go b/fvm/evm/stdlib/contract_test.go index 22679b5a504..ca196ad6482 100644 --- a/fvm/evm/stdlib/contract_test.go +++ b/fvm/evm/stdlib/contract_test.go @@ -24,8 +24,7 @@ import ( type testContractHandler struct { flowTokenAddress common.Address - allocateAddress func() types.Address - addressIndex uint64 + deployCOA func(uint64) types.Address accountByAddress func(types.Address, bool) types.Account lastExecutedBlock func() *types.Block run func(tx []byte, coinbase types.Address) @@ -37,14 +36,13 @@ func (t *testContractHandler) FlowTokenAddress() common.Address { var _ types.ContractHandler = &testContractHandler{} -func (t *testContractHandler) DeployCOA() types.Address { - if t.allocateAddress == nil { - t.addressIndex++ +func (t *testContractHandler) DeployCOA(uuid uint64) types.Address { + if t.deployCOA == nil { var address types.Address - binary.LittleEndian.PutUint64(address[:], t.addressIndex) + binary.LittleEndian.PutUint64(address[:], uuid) return address } - return t.allocateAddress() + return t.deployCOA(uuid) } func (t *testContractHandler) AccountByAddress(addr types.Address, isAuthorized bool) types.Account { @@ -2859,13 +2857,18 @@ func TestEVMCreateBridgedAccount(t *testing.T) { t.Parallel() - handler := &testContractHandler{} + uuidCounter := uint64(0) + handler := &testContractHandler{ + deployCOA: func(uuid uint64) types.Address { + require.Equal(t, uuidCounter, uuid) + return types.Address{uint8(uuidCounter)} + }, + } contractsAddress := flow.BytesToAddress([]byte{0x1}) transactionEnvironment := newEVMTransactionEnvironment(handler, contractsAddress) scriptEnvironment := newEVMScriptEnvironment(handler, contractsAddress) - rt := runtime.NewInterpreterRuntime(runtime.Config{}) script := []byte(` @@ -2908,6 +2911,10 @@ func TestEVMCreateBridgedAccount(t *testing.T) { OnDecodeArgument: func(b []byte, t cadence.Type) (cadence.Value, error) { return json.Decode(nil, b) }, + OnGenerateUUID: func() (uint64, error) { + uuidCounter++ + return uuidCounter, nil + }, } nextTransactionLocation := NewTransactionLocationGenerator() @@ -2940,7 +2947,7 @@ func TestEVMCreateBridgedAccount(t *testing.T) { require.NoError(t, err) expected := cadence.NewArray([]cadence.Value{ - cadence.UInt8(2), cadence.UInt8(0), + cadence.UInt8(4), cadence.UInt8(0), cadence.UInt8(0), cadence.UInt8(0), cadence.UInt8(0), cadence.UInt8(0), cadence.UInt8(0), cadence.UInt8(0), @@ -2967,7 +2974,7 @@ func TestBridgedAccountCall(t *testing.T) { handler := &testContractHandler{ accountByAddress: func(fromAddress types.Address, isAuthorized bool) types.Account { - assert.Equal(t, types.Address{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, fromAddress) + assert.Equal(t, types.Address{3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, fromAddress) assert.True(t, isAuthorized) return &testFlowAccount{ @@ -3091,7 +3098,7 @@ func TestEVMAddressDeposit(t *testing.T) { handler := &testContractHandler{ accountByAddress: func(fromAddress types.Address, isAuthorized bool) types.Account { - assert.Equal(t, types.Address{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, fromAddress) + assert.Equal(t, types.Address{5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, fromAddress) assert.False(t, isAuthorized) return &testFlowAccount{ @@ -3209,7 +3216,7 @@ func TestBridgedAccountWithdraw(t *testing.T) { handler := &testContractHandler{ flowTokenAddress: common.Address(contractsAddress), accountByAddress: func(fromAddress types.Address, isAuthorized bool) types.Account { - assert.Equal(t, types.Address{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, fromAddress) + assert.Equal(t, types.Address{5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, fromAddress) assert.Equal(t, deposited, isAuthorized) return &testFlowAccount{ @@ -3333,11 +3340,10 @@ func TestBridgedAccountDeploy(t *testing.T) { expectedBalance, err := cadence.NewUFix64FromParts(1, 23000000) require.NoError(t, err) - var handler *testContractHandler - handler = &testContractHandler{ + handler := &testContractHandler{ flowTokenAddress: common.Address(contractsAddress), accountByAddress: func(fromAddress types.Address, isAuthorized bool) types.Account { - assert.Equal(t, types.Address{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, fromAddress) + assert.Equal(t, types.Address{3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, fromAddress) assert.True(t, isAuthorized) return &testFlowAccount{ @@ -3348,7 +3354,7 @@ func TestBridgedAccountDeploy(t *testing.T) { assert.Equal(t, types.GasLimit(9999), limit) assert.Equal(t, types.NewBalanceFromUFix64(expectedBalance), balance) - return handler.DeployCOA() + return types.Address{4} }, } }, @@ -3432,7 +3438,7 @@ func TestBridgedAccountDeploy(t *testing.T) { require.NoError(t, err) expected := cadence.NewArray([]cadence.Value{ - cadence.UInt8(2), cadence.UInt8(0), + cadence.UInt8(4), cadence.UInt8(0), cadence.UInt8(0), cadence.UInt8(0), cadence.UInt8(0), cadence.UInt8(0), cadence.UInt8(0), cadence.UInt8(0), @@ -3468,7 +3474,7 @@ func TestEVMAccountBalance(t *testing.T) { handler := &testContractHandler{ flowTokenAddress: common.Address(contractsAddress), accountByAddress: func(fromAddress types.Address, isAuthorized bool) types.Account { - assert.Equal(t, types.Address{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, fromAddress) + assert.Equal(t, types.Address{3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, fromAddress) assert.False(t, isAuthorized) return &testFlowAccount{ @@ -3568,7 +3574,7 @@ func TestEVMAccountBalanceForABIOnlyContract(t *testing.T) { handler := &testContractHandler{ flowTokenAddress: common.Address(contractsAddress), accountByAddress: func(fromAddress types.Address, isAuthorized bool) types.Account { - assert.Equal(t, types.Address{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, fromAddress) + assert.Equal(t, types.Address{5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, fromAddress) assert.False(t, isAuthorized) return &testFlowAccount{ diff --git a/fvm/evm/types/address.go b/fvm/evm/types/address.go index 134ae6c6cf8..8fe7dc18114 100644 --- a/fvm/evm/types/address.go +++ b/fvm/evm/types/address.go @@ -1,11 +1,26 @@ package types import ( - "math/big" + "bytes" gethCommon "github.com/ethereum/go-ethereum/common" ) +const ( + // number of prefix bytes with specific values for special accounts (extended precompiles and COAs) + // using leading zeros for prefix helps with the storage compactness + FlowEVMSpecialAddressPrefixLen = 12 +) + +var ( + // Prefix for the built-in EVM precompiles + FlowEVMNativePrecompileAddressPrefix = [FlowEVMSpecialAddressPrefixLen]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + // Prefix for the extended precompiles + FlowEVMExtendedPrecompileAddressPrefix = [FlowEVMSpecialAddressPrefixLen]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} + // Prefix for the COA addresses + FlowEVMCOAAddressPrefix = [FlowEVMSpecialAddressPrefixLen]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2} +) + // Address is an EVM-compatible address type Address gethCommon.Address @@ -40,13 +55,12 @@ func NewAddressFromString(str string) Address { return NewAddressFromBytes([]byte(str)) } -type GasLimit uint64 - -type Code []byte - -type Data []byte +// IsACOAAddress returns true if the address is a COA address +func IsACOAAddress(addr Address) bool { + return bytes.HasPrefix(addr[:], FlowEVMCOAAddressPrefix[:]) +} -// AsBigInt process the data and return it as a big integer -func (d Data) AsBigInt() *big.Int { - return new(big.Int).SetBytes(d) +// IsAnExtendedPrecompileAddress returns true if the address is a extended precompile address +func IsAnExtendedPrecompileAddress(addr Address) bool { + return bytes.HasPrefix(addr[:], FlowEVMExtendedPrecompileAddressPrefix[:]) } diff --git a/fvm/evm/types/call.go b/fvm/evm/types/call.go index 9401bcf54b8..ec8c7d34820 100644 --- a/fvm/evm/types/call.go +++ b/fvm/evm/types/call.go @@ -170,3 +170,14 @@ func NewContractCall( GasLimit: gasLimit, } } + +type GasLimit uint64 + +type Code []byte + +type Data []byte + +// AsBigInt process the data and return it as a big integer +func (d Data) AsBigInt() *big.Int { + return new(big.Int).SetBytes(d) +} diff --git a/fvm/evm/types/handler.go b/fvm/evm/types/handler.go index afe9255a90e..d49b02bfd43 100644 --- a/fvm/evm/types/handler.go +++ b/fvm/evm/types/handler.go @@ -25,7 +25,7 @@ import ( // ContractHandler handles operations on the evm environment type ContractHandler interface { // DeployCOA deploys a Cadence owned account and return the address - DeployCOA() Address + DeployCOA(uuid uint64) Address // AccountByAddress returns an account by address // if isAuthorized is set, it allows for functionality like `call`, `deploy` @@ -55,7 +55,7 @@ type Backend interface { // AddressAllocator allocates addresses, used by the handler type AddressAllocator interface { // AllocateAddress allocates an address to be used by a COA resource - AllocateCOAAddress() (Address, error) + AllocateCOAAddress(uuid uint64) Address // COAFactoryAddress returns the address for the COA factory COAFactoryAddress() Address