diff --git a/fvm/evm/handler/handler.go b/fvm/evm/handler/handler.go index 861d8dec88c..679d73c7191 100644 --- a/fvm/evm/handler/handler.go +++ b/fvm/evm/handler/handler.go @@ -366,25 +366,32 @@ func (a *Account) Address() types.Address { // Nonce returns the nonce of this account // -// TODO: we might need to meter computation for read only operations as well -// currently the storage limits is enforced +// Note: we don't meter any extra computation given reading data +// from the storage already transalates into computation func (a *Account) Nonce() uint64 { - ctx, err := a.fch.getBlockContext() + nonce, err := a.nonce() panicOnAnyError(err) + return nonce +} - blk, err := a.fch.emulator.NewReadOnlyBlockView(ctx) - panicOnAnyError(err) +func (a *Account) nonce() (uint64, error) { + ctx, err := a.fch.getBlockContext() + if err != nil { + return 0, err + } - nonce, err := blk.NonceOf(a.address) - panicOnAnyError(err) + blk, err := a.fch.emulator.NewReadOnlyBlockView(ctx) + if err != nil { + return 0, err + } - return nonce + return blk.NonceOf(a.address) } // Balance returns the balance of this account // -// TODO: we might need to meter computation for read only operations as well -// currently the storage limits is enforced +// Note: we don't meter any extra computation given reading data +// from the storage already transalates into computation func (a *Account) Balance() types.Balance { bal, err := a.balance() panicOnAnyError(err) @@ -407,6 +414,9 @@ func (a *Account) balance() (types.Balance, error) { } // Code returns the code of this account +// +// Note: we don't meter any extra computation given reading data +// from the storage already transalates into computation func (a *Account) Code() types.Code { code, err := a.code() panicOnAnyError(err) @@ -427,13 +437,15 @@ func (a *Account) code() (types.Code, error) { } // CodeHash returns the code hash of this account +// +// Note: we don't meter any extra computation given reading data +// from the storage already transalates into computation func (a *Account) CodeHash() []byte { codeHash, err := a.codeHash() panicOnAnyError(err) return codeHash } -// CodeHash returns the code hash of this account func (a *Account) codeHash() ([]byte, error) { ctx, err := a.fch.getBlockContext() if err != nil { diff --git a/fvm/evm/stdlib/contract.cdc b/fvm/evm/stdlib/contract.cdc index a9c04aef135..32221a2d2bc 100644 --- a/fvm/evm/stdlib/contract.cdc +++ b/fvm/evm/stdlib/contract.cdc @@ -28,6 +28,30 @@ contract EVM { ) return Balance(attoflow: balance) } + + /// Nonce of the address + access(all) + fun nonce(): UInt64 { + return InternalEVM.nonce( + address: self.bytes + ) + } + + /// Code of the address + access(all) + fun code(): [UInt8] { + return InternalEVM.code( + address: self.bytes + ) + } + + /// CodeHash of the address + access(all) + fun codeHash(): [UInt8] { + return InternalEVM.codeHash( + address: self.bytes + ) + } } access(all) diff --git a/fvm/evm/stdlib/contract.go b/fvm/evm/stdlib/contract.go index 3628734417b..fb407d7396e 100644 --- a/fvm/evm/stdlib/contract.go +++ b/fvm/evm/stdlib/contract.go @@ -1342,6 +1342,132 @@ func newInternalEVMTypeBalanceFunction( ) } +const internalEVMTypeNonceFunctionName = "nonce" + +var internalEVMTypeNonceFunctionType = &sema.FunctionType{ + Parameters: []sema.Parameter{ + { + Label: "address", + TypeAnnotation: sema.NewTypeAnnotation(evmAddressBytesType), + }, + }, + ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.UInt64Type), +} + +// newInternalEVMTypeNonceFunction returns the nonce of the account +func newInternalEVMTypeNonceFunction( + gauge common.MemoryGauge, + handler types.ContractHandler, +) *interpreter.HostFunctionValue { + return interpreter.NewHostFunctionValue( + gauge, + internalEVMTypeCallFunctionType, + func(invocation interpreter.Invocation) interpreter.Value { + inter := invocation.Interpreter + locationRange := invocation.LocationRange + + addressValue, ok := invocation.Arguments[0].(*interpreter.ArrayValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + address, err := AddressBytesArrayValueToEVMAddress(inter, locationRange, addressValue) + if err != nil { + panic(err) + } + + const isAuthorized = false + account := handler.AccountByAddress(address, isAuthorized) + + return interpreter.UInt64Value(account.Nonce()) + }, + ) +} + +const internalEVMTypeCodeFunctionName = "code" + +var internalEVMTypeCodeFunctionType = &sema.FunctionType{ + Parameters: []sema.Parameter{ + { + Label: "address", + TypeAnnotation: sema.NewTypeAnnotation(evmAddressBytesType), + }, + }, + ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.ByteArrayType), +} + +// newInternalEVMTypeCodeFunction returns the code of the account +func newInternalEVMTypeCodeFunction( + gauge common.MemoryGauge, + handler types.ContractHandler, +) *interpreter.HostFunctionValue { + return interpreter.NewHostFunctionValue( + gauge, + internalEVMTypeCallFunctionType, + func(invocation interpreter.Invocation) interpreter.Value { + inter := invocation.Interpreter + locationRange := invocation.LocationRange + + addressValue, ok := invocation.Arguments[0].(*interpreter.ArrayValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + address, err := AddressBytesArrayValueToEVMAddress(inter, locationRange, addressValue) + if err != nil { + panic(err) + } + + const isAuthorized = false + account := handler.AccountByAddress(address, isAuthorized) + + return interpreter.ByteSliceToByteArrayValue(inter, account.Code()) + }, + ) +} + +const internalEVMTypeCodeHashFunctionName = "codeHash" + +var internalEVMTypeCodeHashFunctionType = &sema.FunctionType{ + Parameters: []sema.Parameter{ + { + Label: "address", + TypeAnnotation: sema.NewTypeAnnotation(evmAddressBytesType), + }, + }, + ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.ByteArrayType), +} + +// newInternalEVMTypeCodeHashFunction returns the code hash of the account +func newInternalEVMTypeCodeHashFunction( + gauge common.MemoryGauge, + handler types.ContractHandler, +) *interpreter.HostFunctionValue { + return interpreter.NewHostFunctionValue( + gauge, + internalEVMTypeCallFunctionType, + func(invocation interpreter.Invocation) interpreter.Value { + inter := invocation.Interpreter + locationRange := invocation.LocationRange + + addressValue, ok := invocation.Arguments[0].(*interpreter.ArrayValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + address, err := AddressBytesArrayValueToEVMAddress(inter, locationRange, addressValue) + if err != nil { + panic(err) + } + + const isAuthorized = false + account := handler.AccountByAddress(address, isAuthorized) + + return interpreter.ByteSliceToByteArrayValue(inter, account.CodeHash()) + }, + ) +} + const internalEVMTypeWithdrawFunctionName = "withdraw" var internalEVMTypeWithdrawFunctionType = &sema.FunctionType{ @@ -1596,6 +1722,9 @@ func NewInternalEVMContractValue( internalEVMTypeWithdrawFunctionName: newInternalEVMTypeWithdrawFunction(gauge, handler), internalEVMTypeDeployFunctionName: newInternalEVMTypeDeployFunction(gauge, handler), internalEVMTypeBalanceFunctionName: newInternalEVMTypeBalanceFunction(gauge, handler), + internalEVMTypeNonceFunctionName: newInternalEVMTypeNonceFunction(gauge, handler), + internalEVMTypeCodeFunctionName: newInternalEVMTypeCodeFunction(gauge, handler), + internalEVMTypeCodeHashFunctionName: newInternalEVMTypeCodeHashFunction(gauge, handler), internalEVMTypeEncodeABIFunctionName: newInternalEVMTypeEncodeABIFunction(gauge, location), internalEVMTypeDecodeABIFunctionName: newInternalEVMTypeDecodeABIFunction(gauge, location), internalEVMTypeCastToAttoFLOWFunctionName: newInternalEVMTypeCastToAttoFLOWFunction(gauge, handler), @@ -1670,6 +1799,24 @@ var InternalEVMContractType = func() *sema.CompositeType { internalEVMTypeBalanceFunctionType, "", ), + sema.NewUnmeteredPublicFunctionMember( + ty, + internalEVMTypeNonceFunctionName, + internalEVMTypeNonceFunctionType, + "", + ), + sema.NewUnmeteredPublicFunctionMember( + ty, + internalEVMTypeCodeFunctionName, + internalEVMTypeCodeFunctionType, + "", + ), + sema.NewUnmeteredPublicFunctionMember( + ty, + internalEVMTypeCodeHashFunctionName, + internalEVMTypeCodeHashFunctionType, + "", + ), sema.NewUnmeteredPublicFunctionMember( ty, internalEVMTypeEncodeABIFunctionName, diff --git a/fvm/evm/stdlib/contract_test.go b/fvm/evm/stdlib/contract_test.go index 1faf2575007..995901cad14 100644 --- a/fvm/evm/stdlib/contract_test.go +++ b/fvm/evm/stdlib/contract_test.go @@ -100,7 +100,7 @@ func (t *testFlowAccount) Balance() types.Balance { } func (t *testFlowAccount) Code() types.Code { - if t.balance == nil { + if t.code == nil { return types.Code{} } return t.code() @@ -3511,49 +3511,18 @@ func TestCadenceOwnedAccountDeploy(t *testing.T) { require.True(t, deployed) } -func TestEVMAccountBalance(t *testing.T) { - - t.Parallel() - - contractsAddress := flow.BytesToAddress([]byte{0x1}) - - expectedBalanceValue := cadence.NewUInt(1013370000000000000) - expectedBalance := cadence. - NewStruct([]cadence.Value{expectedBalanceValue}). - WithType(stdlib.NewBalanceCadenceType(common.Address(contractsAddress))) - - handler := &testContractHandler{ - flowTokenAddress: common.Address(contractsAddress), - accountByAddress: func(fromAddress types.Address, isAuthorized bool) types.Account { - 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{ - address: fromAddress, - balance: func() types.Balance { - return types.NewBalance(expectedBalanceValue.Value) - }, - } - }, - } - +func RunEVMScript( + t *testing.T, + handler *testContractHandler, + script []byte, + expectedValue cadence.Value, +) { + contractsAddress := flow.Address(handler.evmContractAddress) transactionEnvironment := newEVMTransactionEnvironment(handler, contractsAddress) scriptEnvironment := newEVMScriptEnvironment(handler, contractsAddress) rt := runtime.NewInterpreterRuntime(runtime.Config{}) - script := []byte(` - import EVM from 0x1 - - access(all) - fun main(): EVM.Balance { - let cadenceOwnedAccount <- EVM.createCadenceOwnedAccount() - let balance = cadenceOwnedAccount.balance() - destroy cadenceOwnedAccount - return balance - } - `) - accountCodes := map[common.Location][]byte{} var events []cadence.Event @@ -3610,7 +3579,162 @@ func TestEVMAccountBalance(t *testing.T) { require.NoError(t, err) require.NoError(t, err) - require.Equal(t, expectedBalance.ToGoValue(), actual.ToGoValue()) + require.Equal(t, expectedValue.ToGoValue(), actual.ToGoValue()) +} + +func TestEVMAccountBalance(t *testing.T) { + t.Parallel() + + contractsAddress := flow.BytesToAddress([]byte{0x1}) + expectedBalanceValue := cadence.NewUInt(1013370000000000000) + expectedBalance := cadence. + NewStruct([]cadence.Value{expectedBalanceValue}). + WithType(stdlib.NewBalanceCadenceType(common.Address(contractsAddress))) + + handler := &testContractHandler{ + flowTokenAddress: common.Address(contractsAddress), + evmContractAddress: common.Address(contractsAddress), + accountByAddress: func(fromAddress types.Address, isAuthorized bool) types.Account { + 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{ + address: fromAddress, + balance: func() types.Balance { + return types.NewBalance(expectedBalanceValue.Value) + }, + } + }, + } + + script := []byte(` + import EVM from 0x1 + + access(all) + fun main(): EVM.Balance { + let cadenceOwnedAccount <- EVM.createCadenceOwnedAccount() + let balance = cadenceOwnedAccount.balance() + destroy cadenceOwnedAccount + return balance + } + `) + RunEVMScript(t, handler, script, expectedBalance) +} + +func TestEVMAccountNonce(t *testing.T) { + t.Parallel() + + contractsAddress := flow.BytesToAddress([]byte{0x1}) + expectedNonceValue := cadence.NewUInt64(2000) + handler := &testContractHandler{ + flowTokenAddress: common.Address(contractsAddress), + evmContractAddress: common.Address(contractsAddress), + accountByAddress: func(fromAddress types.Address, isAuthorized bool) types.Account { + 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{ + address: fromAddress, + nonce: func() uint64 { + return uint64(expectedNonceValue) + }, + } + }, + } + + script := []byte(` + import EVM from 0x1 + + access(all) + fun main(): UInt64 { + let cadenceOwnedAccount <- EVM.createCadenceOwnedAccount() + let nonce = cadenceOwnedAccount.address().nonce() + destroy cadenceOwnedAccount + return nonce + } + `) + + RunEVMScript(t, handler, script, expectedNonceValue) +} + +func TestEVMAccountCode(t *testing.T) { + t.Parallel() + + contractsAddress := flow.BytesToAddress([]byte{0x1}) + expectedCodeRaw := []byte{1, 2, 3} + expectedCodeValue := cadence.NewArray( + []cadence.Value{cadence.UInt8(1), cadence.UInt8(2), cadence.UInt8(3)}, + ).WithType(cadence.NewVariableSizedArrayType(cadence.TheUInt8Type)) + + handler := &testContractHandler{ + flowTokenAddress: common.Address(contractsAddress), + evmContractAddress: common.Address(contractsAddress), + accountByAddress: func(fromAddress types.Address, isAuthorized bool) types.Account { + 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{ + address: fromAddress, + code: func() types.Code { + return expectedCodeRaw + }, + } + }, + } + + script := []byte(` + import EVM from 0x1 + + access(all) + fun main(): [UInt8] { + let cadenceOwnedAccount <- EVM.createCadenceOwnedAccount() + let code = cadenceOwnedAccount.address().code() + destroy cadenceOwnedAccount + return code + } + `) + + RunEVMScript(t, handler, script, expectedCodeValue) +} + +func TestEVMAccountCodeHash(t *testing.T) { + t.Parallel() + + contractsAddress := flow.BytesToAddress([]byte{0x1}) + expectedCodeHashRaw := []byte{1, 2, 3} + expectedCodeHashValue := cadence.NewArray( + []cadence.Value{cadence.UInt8(1), cadence.UInt8(2), cadence.UInt8(3)}, + ).WithType(cadence.NewVariableSizedArrayType(cadence.TheUInt8Type)) + + handler := &testContractHandler{ + flowTokenAddress: common.Address(contractsAddress), + evmContractAddress: common.Address(contractsAddress), + accountByAddress: func(fromAddress types.Address, isAuthorized bool) types.Account { + 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{ + address: fromAddress, + codeHash: func() []byte { + return expectedCodeHashRaw + }, + } + }, + } + + script := []byte(` + import EVM from 0x1 + + access(all) + fun main(): [UInt8] { + let cadenceOwnedAccount <- EVM.createCadenceOwnedAccount() + let codeHash = cadenceOwnedAccount.address().codeHash() + destroy cadenceOwnedAccount + return codeHash + } + `) + + RunEVMScript(t, handler, script, expectedCodeHashValue) } func TestEVMAccountBalanceForABIOnlyContract(t *testing.T) {