From b31e5980e7f8e8abd73defe125360554d0c21d04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Thu, 26 Oct 2023 22:18:50 -0700 Subject: [PATCH 1/6] implement EVM.BridgedAccount.withdraw --- fvm/evm/evm.go | 4 +- fvm/evm/evm_test.go | 51 +++++++++++++++- fvm/evm/handler/handler.go | 17 ++++-- fvm/evm/handler/handler_test.go | 48 +++++++++++---- fvm/evm/stdlib/contract.cdc | 14 +++-- fvm/evm/stdlib/contract.go | 82 +++++++++++++++++++++++++ fvm/evm/stdlib/contract_test.go | 103 ++++++++++++++++++++++++++++++++ fvm/evm/types/handler.go | 4 ++ fvm/script.go | 1 + fvm/transactionInvoker.go | 1 + 10 files changed, 302 insertions(+), 23 deletions(-) diff --git a/fvm/evm/evm.go b/fvm/evm/evm.go index ada57017943..ced702f7327 100644 --- a/fvm/evm/evm.go +++ b/fvm/evm/evm.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/onflow/cadence/runtime" + "github.com/onflow/cadence/runtime/common" "github.com/onflow/flow-go/fvm/environment" evm "github.com/onflow/flow-go/fvm/evm/emulator" @@ -29,6 +30,7 @@ func SetupEnvironment( backend types.Backend, env runtime.Environment, service flow.Address, + flowToken flow.Address, ) error { // TODO: setup proper root address based on chainID evmRootAddress, err := RootAccountAddress(chainID) @@ -48,7 +50,7 @@ func SetupEnvironment( return err } - contractHandler := handler.NewContractHandler(bs, backend, em) + contractHandler := handler.NewContractHandler(common.Address(flowToken), bs, backend, em) stdlib.SetupEnvironment(env, contractHandler, service) diff --git a/fvm/evm/evm_test.go b/fvm/evm/evm_test.go index 2b7f28728d1..201806ee622 100644 --- a/fvm/evm/evm_test.go +++ b/fvm/evm/evm_test.go @@ -112,7 +112,7 @@ func RunWithNewTestVM(t *testing.T, chain flow.Chain, f func(fvm.Context, fvm.VM f(ctx, vm, snapshotTree) } -// TODO: test with actual amount +// TODO: deposit non-zero amount func TestEVMAddressDeposit(t *testing.T) { t.Parallel() @@ -159,3 +159,52 @@ func TestEVMAddressDeposit(t *testing.T) { }) }) } + +// TODO: deposit non-zero amount +func TestEVMAddressDepositWithdraw(t *testing.T) { + + t.Parallel() + + RunWithTestBackend(t, func(backend types.Backend) { + RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { + RunWithDeployedContract(t, backend, rootAddr, func(testContract *TestContract) { + RunWithEOATestAccount(t, backend, rootAddr, func(testAccount *EOATestAccount) { + chain := flow.Emulator.Chain() + RunWithNewTestVM(t, chain, func(ctx fvm.Context, vm fvm.VM, snapshot snapshot.SnapshotTree) { + + code := []byte(fmt.Sprintf( + ` + import EVM from %s + import FlowToken from %s + + access(all) + fun main(): UFix64 { + let bridgedAccount <- EVM.createBridgedAccount() + let vault <- bridgedAccount.withdraw(balance: EVM.Balance(flow: 0.0)) + let balance = vault.balance + destroy bridgedAccount + destroy vault + return balance + } + `, + chain.ServiceAddress().HexWithPrefix(), + fvm.FlowTokenAddress(chain).HexWithPrefix(), + )) + + script := fvm.Script(code) + + executionSnapshot, output, err := vm.Run( + ctx, + script, + snapshot) + require.NoError(t, err) + require.NoError(t, output.Err) + + // TODO: + _ = executionSnapshot + }) + }) + }) + }) + }) +} diff --git a/fvm/evm/handler/handler.go b/fvm/evm/handler/handler.go index f8224c47d22..fc8481bd307 100644 --- a/fvm/evm/handler/handler.go +++ b/fvm/evm/handler/handler.go @@ -7,6 +7,7 @@ import ( gethCommon "github.com/ethereum/go-ethereum/common" gethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rlp" + "github.com/onflow/cadence/runtime/common" "github.com/onflow/flow-go/fvm/environment" "github.com/onflow/flow-go/fvm/errors" @@ -24,6 +25,7 @@ import ( // in the future we might benefit from a view style of access to db passed as // a param to the emulator. type ContractHandler struct { + flowTokenAddress common.Address blockchain types.BlockChain backend types.Backend emulator types.Emulator @@ -32,9 +34,14 @@ type ContractHandler struct { totalSupply uint64 } +func (h *ContractHandler) FlowTokenAddress() common.Address { + return h.flowTokenAddress +} + var _ types.ContractHandler = &ContractHandler{} func NewContractHandler( + flowTokenAddress common.Address, blockchain types.BlockChain, backend types.Backend, emulator types.Emulator, @@ -46,6 +53,7 @@ func NewContractHandler( } return &ContractHandler{ + flowTokenAddress: flowTokenAddress, blockchain: blockchain, backend: backend, emulator: emulator, @@ -78,7 +86,7 @@ func (h *ContractHandler) AccountByAddress(addr types.Address, isAuthorized bool } // LastExecutedBlock returns the last executed block -func (h ContractHandler) LastExecutedBlock() *types.Block { +func (h *ContractHandler) LastExecutedBlock() *types.Block { block, err := h.blockchain.LatestBlock() handleError(err) return block @@ -98,7 +106,7 @@ func (h *ContractHandler) updateLastExecutedBlock(stateRoot, eventRoot gethCommo } // Run runs an rlpencoded evm transaction, collect the evm fees under the provided coinbase -func (h ContractHandler) Run(rlpEncodedTx []byte, coinbase types.Address) bool { +func (h *ContractHandler) Run(rlpEncodedTx []byte, coinbase types.Address) bool { // Decode transaction encoding tx := gethTypes.Transaction{} @@ -149,14 +157,14 @@ func (h ContractHandler) Run(rlpEncodedTx []byte, coinbase types.Address) bool { return !failed } -func (h ContractHandler) checkGasLimit(limit types.GasLimit) { +func (h *ContractHandler) checkGasLimit(limit types.GasLimit) { // check gas limit against what has been left on the transaction side if !h.backend.ComputationAvailable(environment.ComputationKindEVMGasUsage, uint(limit)) { handleError(types.ErrInsufficientComputation) } } -func (h ContractHandler) meterGasUsage(res *types.Result) { +func (h *ContractHandler) meterGasUsage(res *types.Result) { if res != nil { err := h.backend.MeterComputation(environment.ComputationKindEVMGasUsage, uint(res.GasConsumed)) handleError(err) @@ -167,6 +175,7 @@ func (h *ContractHandler) EmitEvent(event *types.Event) { // TODO add extra metering for rlp encoding encoded, err := event.Payload.Encode() handleError(err) + // TODO: handle error h.backend.EmitFlowEvent(event.Etype, encoded) } diff --git a/fvm/evm/handler/handler_test.go b/fvm/evm/handler/handler_test.go index d2db9593f14..5a87c929208 100644 --- a/fvm/evm/handler/handler_test.go +++ b/fvm/evm/handler/handler_test.go @@ -11,9 +11,11 @@ import ( gethTypes "github.com/ethereum/go-ethereum/core/types" gethParams "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" + "github.com/onflow/cadence/runtime/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/onflow/flow-go/fvm" "github.com/onflow/flow-go/fvm/errors" "github.com/onflow/flow-go/fvm/evm/emulator" "github.com/onflow/flow-go/fvm/evm/emulator/database" @@ -25,10 +27,14 @@ import ( // TODO add test for fatal errors +var flowTokenAddress = common.Address(fvm.FlowTokenAddress(flow.Emulator.Chain())) + func TestHandler_TransactionRun(t *testing.T) { t.Parallel() t.Run("test - transaction run (happy case)", func(t *testing.T) { + t.Parallel() + testutils.RunWithTestBackend(t, func(backend types.Backend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { testutils.RunWithEOATestAccount(t, backend, rootAddr, func(eoa *testutils.EOATestAccount) { @@ -52,7 +58,7 @@ func TestHandler_TransactionRun(t *testing.T) { return result, nil }, } - handler := handler.NewContractHandler(bs, backend, em) + handler := handler.NewContractHandler(flowTokenAddress, bs, backend, em) coinbase := types.NewAddress(gethCommon.Address{}) @@ -98,6 +104,8 @@ func TestHandler_TransactionRun(t *testing.T) { }) t.Run("test - transaction run (unhappy cases)", func(t *testing.T) { + t.Parallel() + testutils.RunWithTestBackend(t, func(backend types.Backend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { testutils.RunWithEOATestAccount(t, backend, rootAddr, func(eoa *testutils.EOATestAccount) { @@ -110,7 +118,7 @@ func TestHandler_TransactionRun(t *testing.T) { return &types.Result{}, types.NewEVMExecutionError(fmt.Errorf("some sort of error")) }, } - handler := handler.NewContractHandler(bs, backend, em) + handler := handler.NewContractHandler(flowTokenAddress, bs, backend, em) coinbase := types.NewAddress(gethCommon.Address{}) @@ -155,6 +163,8 @@ func TestHandler_TransactionRun(t *testing.T) { }) t.Run("test running transaction (with integrated emulator)", func(t *testing.T) { + t.Parallel() + testutils.RunWithTestBackend(t, func(backend types.Backend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { @@ -166,7 +176,7 @@ func TestHandler_TransactionRun(t *testing.T) { emulator := emulator.NewEmulator(db) - handler := handler.NewContractHandler(bs, backend, emulator) + handler := handler.NewContractHandler(flowTokenAddress, bs, backend, emulator) eoa := testutils.GetTestEOAAccount(t, testutils.EOATestAccount1KeyHex) @@ -218,12 +228,14 @@ func TestHandler_OpsWithoutEmulator(t *testing.T) { t.Parallel() t.Run("test last executed block call", func(t *testing.T) { + t.Parallel() + testutils.RunWithTestBackend(t, func(backend types.Backend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { bs, err := handler.NewBlockStore(backend, rootAddr) require.NoError(t, err) - handler := handler.NewContractHandler(bs, backend, nil) + handler := handler.NewContractHandler(flowTokenAddress, bs, backend, nil) // test call last executed block without initialization b := handler.LastExecutedBlock() require.Equal(t, types.GenesisBlock, b) @@ -241,12 +253,14 @@ func TestHandler_OpsWithoutEmulator(t *testing.T) { }) t.Run("test address allocation", func(t *testing.T) { + t.Parallel() + testutils.RunWithTestBackend(t, func(backend types.Backend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { blockchain, err := handler.NewBlockStore(backend, rootAddr) require.NoError(t, err) - handler := handler.NewContractHandler(blockchain, backend, nil) + handler := handler.NewContractHandler(flowTokenAddress, blockchain, backend, nil) foa := handler.AllocateAddress() require.NotNil(t, foa) @@ -260,6 +274,8 @@ func TestHandler_OpsWithoutEmulator(t *testing.T) { func TestHandler_FOA(t *testing.T) { t.Run("test deposit/withdraw (with integrated emulator)", func(t *testing.T) { + t.Parallel() + testutils.RunWithTestBackend(t, func(backend types.Backend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { bs, err := handler.NewBlockStore(backend, rootAddr) @@ -270,7 +286,7 @@ func TestHandler_FOA(t *testing.T) { emulator := emulator.NewEmulator(db) - handler := handler.NewContractHandler(bs, backend, emulator) + handler := handler.NewContractHandler(flowTokenAddress, bs, backend, emulator) foa := handler.AccountByAddress(handler.AllocateAddress(), true) require.NotNil(t, foa) @@ -331,6 +347,8 @@ func TestHandler_FOA(t *testing.T) { }) t.Run("test withdraw (unhappy case)", func(t *testing.T) { + t.Parallel() + testutils.RunWithTestBackend(t, func(backend types.Backend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { testutils.RunWithEOATestAccount(t, backend, rootAddr, func(eoa *testutils.EOATestAccount) { @@ -340,7 +358,7 @@ func TestHandler_FOA(t *testing.T) { // Withdraw calls are only possible within FOA accounts assertPanic(t, types.IsAUnAuthroizedMethodCallError, func() { em := &testutils.TestEmulator{} - handler := handler.NewContractHandler(bs, backend, em) + handler := handler.NewContractHandler(flowTokenAddress, bs, backend, em) account := handler.AccountByAddress(testutils.RandomAddress(), false) account.Withdraw(types.Balance(1)) @@ -353,7 +371,7 @@ func TestHandler_FOA(t *testing.T) { return &types.Result{}, types.NewEVMExecutionError(fmt.Errorf("some sort of error")) }, } - handler := handler.NewContractHandler(bs, backend, em) + handler := handler.NewContractHandler(flowTokenAddress, bs, backend, em) account := handler.AccountByAddress(testutils.RandomAddress(), true) account.Withdraw(types.Balance(1)) }) @@ -365,7 +383,7 @@ func TestHandler_FOA(t *testing.T) { return &types.Result{}, types.NewEVMExecutionError(fmt.Errorf("some sort of error")) }, } - handler := handler.NewContractHandler(bs, backend, em) + handler := handler.NewContractHandler(flowTokenAddress, bs, backend, em) account := handler.AccountByAddress(testutils.RandomAddress(), true) account.Withdraw(types.Balance(0)) }) @@ -377,7 +395,7 @@ func TestHandler_FOA(t *testing.T) { return &types.Result{}, types.NewFatalError(fmt.Errorf("some sort of fatal error")) }, } - handler := handler.NewContractHandler(bs, backend, em) + handler := handler.NewContractHandler(flowTokenAddress, bs, backend, em) account := handler.AccountByAddress(testutils.RandomAddress(), true) account.Withdraw(types.Balance(0)) }) @@ -387,6 +405,8 @@ func TestHandler_FOA(t *testing.T) { }) t.Run("test deposit (unhappy case)", func(t *testing.T) { + t.Parallel() + testutils.RunWithTestBackend(t, func(backend types.Backend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { testutils.RunWithEOATestAccount(t, backend, rootAddr, func(eoa *testutils.EOATestAccount) { @@ -400,7 +420,7 @@ func TestHandler_FOA(t *testing.T) { return nil, types.NewEVMExecutionError(fmt.Errorf("some sort of error")) }, } - handler := handler.NewContractHandler(bs, backend, em) + handler := handler.NewContractHandler(flowTokenAddress, bs, backend, em) account := handler.AccountByAddress(testutils.RandomAddress(), true) account.Deposit(types.NewFlowTokenVault(1)) }) @@ -412,7 +432,7 @@ func TestHandler_FOA(t *testing.T) { return nil, types.NewFatalError(fmt.Errorf("some sort of fatal error")) }, } - handler := handler.NewContractHandler(bs, backend, em) + handler := handler.NewContractHandler(flowTokenAddress, bs, backend, em) account := handler.AccountByAddress(testutils.RandomAddress(), true) account.Deposit(types.NewFlowTokenVault(1)) }) @@ -422,6 +442,8 @@ func TestHandler_FOA(t *testing.T) { }) t.Run("test deploy/call (with integrated emulator)", func(t *testing.T) { + t.Parallel() + // TODO update this test with events, gas metering, etc testutils.RunWithTestBackend(t, func(backend types.Backend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { @@ -433,7 +455,7 @@ func TestHandler_FOA(t *testing.T) { emulator := emulator.NewEmulator(db) - handler := handler.NewContractHandler(bs, backend, emulator) + handler := handler.NewContractHandler(flowTokenAddress, bs, backend, emulator) foa := handler.AccountByAddress(handler.AllocateAddress(), true) require.NotNil(t, foa) diff --git a/fvm/evm/stdlib/contract.cdc b/fvm/evm/stdlib/contract.cdc index 15fb9124533..9399a490d34 100644 --- a/fvm/evm/stdlib/contract.cdc +++ b/fvm/evm/stdlib/contract.cdc @@ -70,11 +70,17 @@ contract EVM { self.address().deposit(from: <-from) } + /// Withdraws the balance from the bridged account's balance + access(all) + fun withdraw(balance: Balance): @FlowToken.Vault { + let vault <- InternalEVM.withdraw( + from: self.addressBytes, + amount: balance.flow + ) as! @FlowToken.Vault + return <-vault + } + // TODO: - // /// Withdraws the balance from the bridged account's balance - // access(all) - // fun withdraw(balance: Balance): @FlowToken.Vault - // // /// Deploys a contract to the EVM environment. // /// Returns the address of the newly deployed contract // access(all) diff --git a/fvm/evm/stdlib/contract.go b/fvm/evm/stdlib/contract.go index ba9dfc8f6ac..474344f3d30 100644 --- a/fvm/evm/stdlib/contract.go +++ b/fvm/evm/stdlib/contract.go @@ -343,6 +343,81 @@ func newInternalEVMTypeDepositFunction( ) } +const internalEVMTypeWithdrawFunctionName = "withdraw" + +var internalEVMTypeWithdrawFunctionType = &sema.FunctionType{ + Parameters: []sema.Parameter{ + { + Label: "from", + TypeAnnotation: sema.NewTypeAnnotation(evmAddressBytesType), + }, + { + Label: "amount", + TypeAnnotation: sema.NewTypeAnnotation(sema.UFix64Type), + }, + }, + ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.AnyResourceType), +} + +func newInternalEVMTypeWithdrawFunction( + 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 + + // Get from address + + fromAddressValue, ok := invocation.Arguments[0].(*interpreter.ArrayValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + fromAddress, err := AddressBytesArrayValueToEVMAddress(inter, locationRange, fromAddressValue) + if err != nil { + panic(err) + } + + // Get amount + + amountValue, ok := invocation.Arguments[1].(interpreter.UFix64Value) + if !ok { + panic(errors.NewUnreachableError()) + } + + amount := types.Balance(amountValue) + + // Call + + const isAuthorized = true + account := handler.AccountByAddress(fromAddress, isAuthorized) + vault := account.Withdraw(amount) + + // TODO: improve: maybe call actual constructor + return interpreter.NewCompositeValue( + inter, + locationRange, + common.NewAddressLocation(gauge, handler.FlowTokenAddress(), "FlowToken"), + "FlowToken.Vault", + common.CompositeKindResource, + []interpreter.CompositeField{ + { + Name: "balance", + Value: interpreter.NewUFix64Value(gauge, func() uint64 { + return uint64(vault.Balance()) + }), + }, + }, + common.ZeroAddress, + ) + }, + ) +} + func NewInternalEVMContractValue( gauge common.MemoryGauge, handler types.ContractHandler, @@ -357,6 +432,7 @@ func NewInternalEVMContractValue( internalEVMTypeCreateBridgedAccountFunctionName: newInternalEVMTypeCreateBridgedAccountFunction(gauge, handler), internalEVMTypeCallFunctionName: newInternalEVMTypeCallFunction(gauge, handler), internalEVMTypeDepositFunctionName: newInternalEVMTypeDepositFunction(gauge, handler), + internalEVMTypeWithdrawFunctionName: newInternalEVMTypeWithdrawFunction(gauge, handler), }, nil, nil, @@ -397,6 +473,12 @@ var InternalEVMContractType = func() *sema.CompositeType { internalEVMTypeDepositFunctionType, "", ), + sema.NewUnmeteredPublicFunctionMember( + ty, + internalEVMTypeWithdrawFunctionName, + internalEVMTypeWithdrawFunctionType, + "", + ), }) return ty }() diff --git a/fvm/evm/stdlib/contract_test.go b/fvm/evm/stdlib/contract_test.go index c061bdf9a56..184f9efe754 100644 --- a/fvm/evm/stdlib/contract_test.go +++ b/fvm/evm/stdlib/contract_test.go @@ -20,6 +20,7 @@ import ( ) type testContractHandler struct { + flowTokenAddress common.Address allocateAddress func() types.Address addressIndex uint64 accountByAddress func(types.Address, bool) types.Account @@ -27,6 +28,10 @@ type testContractHandler struct { run func(tx []byte, coinbase types.Address) bool } +func (t *testContractHandler) FlowTokenAddress() common.Address { + return t.flowTokenAddress +} + var _ types.ContractHandler = &testContractHandler{} func (t *testContractHandler) AllocateAddress() types.Address { @@ -716,6 +721,7 @@ func TestBridgedAccountCall(t *testing.T) { require.Equal(t, expected, actual) } +// TODO: deposit non-zero amount func TestEVMAddressDeposit(t *testing.T) { t.Parallel() @@ -809,3 +815,100 @@ func TestEVMAddressDeposit(t *testing.T) { require.True(t, deposited) } + +func TestEVMAddressDepositWithdraw(t *testing.T) { + + t.Parallel() + + var withdrew bool + + contractsAddress := flow.BytesToAddress([]byte{0x1}) + + 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.False(t, isAuthorized) + + return &testFlowAccount{ + address: fromAddress, + withdraw: func(balance types.Balance) *types.FLOWTokenVault { + withdrew = true + return types.NewFlowTokenVault(balance) + }, + } + }, + } + + env := runtime.NewBaseInterpreterEnvironment(runtime.Config{}) + + stdlib.SetupEnvironment(env, handler, contractsAddress) + + rt := runtime.NewInterpreterRuntime(runtime.Config{}) + + script := []byte(` + import EVM from 0x1 + import FlowToken from 0x1 + + access(all) + fun main(): UFix64 { + let bridgedAccount <- EVM.createBridgedAccount() + let vault <- bridgedAccount.withdraw(balance: EVM.Balance(flow: 0.0)) + let balance = vault.balance + destroy bridgedAccount + destroy vault + return balance + } + `) + + accountCodes := map[common.Location][]byte{} + var events []cadence.Event + + runtimeInterface := &TestRuntimeInterface{ + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]runtime.Address, error) { + return []runtime.Address{runtime.Address(contractsAddress)}, nil + }, + OnResolveLocation: SingleIdentifierLocationResolver(t), + OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error { + accountCodes[location] = code + return nil + }, + OnGetAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { + code = accountCodes[location] + return code, nil + }, + OnEmitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, + OnDecodeArgument: func(b []byte, t cadence.Type) (cadence.Value, error) { + return json.Decode(nil, b) + }, + } + + nextTransactionLocation := NewTransactionLocationGenerator() + nextScriptLocation := NewScriptLocationGenerator() + + // Deploy contracts + + deployContracts(t, rt, contractsAddress, runtimeInterface, env, nextTransactionLocation) + + // Run script + + result, err := rt.ExecuteScript( + runtime.Script{ + Source: script, + }, + runtime.Context{ + Interface: runtimeInterface, + Environment: env, + Location: nextScriptLocation(), + }, + ) + require.NoError(t, err) + + require.Equal(t, cadence.UFix64(0), result) + + require.True(t, withdrew) +} diff --git a/fvm/evm/types/handler.go b/fvm/evm/types/handler.go index cb7f64ea7e4..0e257c33ea5 100644 --- a/fvm/evm/types/handler.go +++ b/fvm/evm/types/handler.go @@ -1,6 +1,8 @@ package types import ( + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/flow-go/fvm/environment" ) @@ -36,6 +38,8 @@ type ContractHandler interface { // collects the gas fees, and transfers the gas fees to the given coinbase account. // Returns true if the transaction was successfully executed Run(tx []byte, coinbase Address) bool + + FlowTokenAddress() common.Address } // Backend passes the FVM functionality needed inside the handler diff --git a/fvm/script.go b/fvm/script.go index eef02fdda39..9c869d96afe 100644 --- a/fvm/script.go +++ b/fvm/script.go @@ -208,6 +208,7 @@ func (executor *scriptExecutor) executeScript() error { executor.env, rt.Environment, chain.ServiceAddress(), + FlowTokenAddress(chain), ) } diff --git a/fvm/transactionInvoker.go b/fvm/transactionInvoker.go index 770363bb29a..c7b035bcc1d 100644 --- a/fvm/transactionInvoker.go +++ b/fvm/transactionInvoker.go @@ -99,6 +99,7 @@ func newTransactionExecutor( env, cadenceRuntime.Environment, chain.ServiceAddress(), + FlowTokenAddress(chain), ) } From a0b3797089da3c67f798ffdd7ea8100645f19879 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 27 Oct 2023 08:57:08 -0700 Subject: [PATCH 2/6] clean up --- fvm/evm/evm_test.go | 4 ++-- fvm/evm/stdlib/contract.go | 4 ++-- fvm/evm/stdlib/contract_test.go | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/fvm/evm/evm_test.go b/fvm/evm/evm_test.go index 201806ee622..d5da986d818 100644 --- a/fvm/evm/evm_test.go +++ b/fvm/evm/evm_test.go @@ -161,7 +161,7 @@ func TestEVMAddressDeposit(t *testing.T) { } // TODO: deposit non-zero amount -func TestEVMAddressDepositWithdraw(t *testing.T) { +func TestEVMAddressWithdraw(t *testing.T) { t.Parallel() @@ -179,7 +179,7 @@ func TestEVMAddressDepositWithdraw(t *testing.T) { access(all) fun main(): UFix64 { - let bridgedAccount <- EVM.createBridgedAccount() + let bridgedAccount <- EVM.createBridgedAccount() let vault <- bridgedAccount.withdraw(balance: EVM.Balance(flow: 0.0)) let balance = vault.balance destroy bridgedAccount diff --git a/fvm/evm/stdlib/contract.go b/fvm/evm/stdlib/contract.go index 474344f3d30..4383891c9fc 100644 --- a/fvm/evm/stdlib/contract.go +++ b/fvm/evm/stdlib/contract.go @@ -332,7 +332,7 @@ func newInternalEVMTypeDepositFunction( amount := types.Balance(amountValue) - // Call + // Deposit const isAuthorized = false account := handler.AccountByAddress(toAddress, isAuthorized) @@ -391,7 +391,7 @@ func newInternalEVMTypeWithdrawFunction( amount := types.Balance(amountValue) - // Call + // Withdraw const isAuthorized = true account := handler.AccountByAddress(fromAddress, isAuthorized) diff --git a/fvm/evm/stdlib/contract_test.go b/fvm/evm/stdlib/contract_test.go index 184f9efe754..23f8faeda1c 100644 --- a/fvm/evm/stdlib/contract_test.go +++ b/fvm/evm/stdlib/contract_test.go @@ -816,7 +816,7 @@ func TestEVMAddressDeposit(t *testing.T) { require.True(t, deposited) } -func TestEVMAddressDepositWithdraw(t *testing.T) { +func TestEVMAddressWithdraw(t *testing.T) { t.Parallel() @@ -852,7 +852,7 @@ func TestEVMAddressDepositWithdraw(t *testing.T) { access(all) fun main(): UFix64 { - let bridgedAccount <- EVM.createBridgedAccount() + let bridgedAccount <- EVM.createBridgedAccount() let vault <- bridgedAccount.withdraw(balance: EVM.Balance(flow: 0.0)) let balance = vault.balance destroy bridgedAccount From e562f288605709ee758811991793e8e6fa3f553b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 27 Oct 2023 08:58:17 -0700 Subject: [PATCH 3/6] fix test --- fvm/evm/stdlib/contract_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fvm/evm/stdlib/contract_test.go b/fvm/evm/stdlib/contract_test.go index 23f8faeda1c..429cee06ac4 100644 --- a/fvm/evm/stdlib/contract_test.go +++ b/fvm/evm/stdlib/contract_test.go @@ -828,7 +828,7 @@ func TestEVMAddressWithdraw(t *testing.T) { 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.False(t, isAuthorized) + assert.True(t, isAuthorized) return &testFlowAccount{ address: fromAddress, From 48ed0e05256174c10da9072afcd82b22105d263b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 27 Oct 2023 09:04:20 -0700 Subject: [PATCH 4/6] assert balance amounts --- fvm/evm/stdlib/contract_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fvm/evm/stdlib/contract_test.go b/fvm/evm/stdlib/contract_test.go index 429cee06ac4..2a779379f5a 100644 --- a/fvm/evm/stdlib/contract_test.go +++ b/fvm/evm/stdlib/contract_test.go @@ -738,7 +738,7 @@ func TestEVMAddressDeposit(t *testing.T) { address: fromAddress, deposit: func(vault *types.FLOWTokenVault) { deposited = true - assert.Zero(t, vault.Balance()) + assert.Equal(t, types.Balance(0), vault.Balance()) }, } }, @@ -833,6 +833,7 @@ func TestEVMAddressWithdraw(t *testing.T) { return &testFlowAccount{ address: fromAddress, withdraw: func(balance types.Balance) *types.FLOWTokenVault { + assert.Equal(t, types.Balance(0), balance) withdrew = true return types.NewFlowTokenVault(balance) }, From 3d8ef864cd58871c26f4f6cf48d3c6c87c70cf19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 27 Oct 2023 09:13:14 -0700 Subject: [PATCH 5/6] fix test names --- fvm/evm/evm_test.go | 2 +- fvm/evm/stdlib/contract_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fvm/evm/evm_test.go b/fvm/evm/evm_test.go index d5da986d818..18e503d6099 100644 --- a/fvm/evm/evm_test.go +++ b/fvm/evm/evm_test.go @@ -161,7 +161,7 @@ func TestEVMAddressDeposit(t *testing.T) { } // TODO: deposit non-zero amount -func TestEVMAddressWithdraw(t *testing.T) { +func TestBridgedAccountWithdraw(t *testing.T) { t.Parallel() diff --git a/fvm/evm/stdlib/contract_test.go b/fvm/evm/stdlib/contract_test.go index 2a779379f5a..36846202371 100644 --- a/fvm/evm/stdlib/contract_test.go +++ b/fvm/evm/stdlib/contract_test.go @@ -816,7 +816,7 @@ func TestEVMAddressDeposit(t *testing.T) { require.True(t, deposited) } -func TestEVMAddressWithdraw(t *testing.T) { +func TestBridgedAccountWithdraw(t *testing.T) { t.Parallel() From 863357b039fdbfda9c901d17e5627108456946dd Mon Sep 17 00:00:00 2001 From: ramtinms Date: Tue, 14 Nov 2023 14:52:31 -0800 Subject: [PATCH 6/6] revert the account not found error on withdraw --- fvm/evm/emulator/emulator.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fvm/evm/emulator/emulator.go b/fvm/evm/emulator/emulator.go index 9371562ef63..3896c83e9b3 100644 --- a/fvm/evm/emulator/emulator.go +++ b/fvm/evm/emulator/emulator.go @@ -235,8 +235,11 @@ func (proc *procedure) withdrawFrom(address types.Address, amount *big.Int) (*ty // while this method is only called from bridged accounts // it might be the case that someone creates a bridged account // and never transfer tokens to and call for withdraw + // TODO: we might revisit this apporach and + // return res, types.ErrAccountDoesNotExist + // instead if !proc.state.Exist(addr) { - return res, types.ErrAccountDoesNotExist + proc.state.CreateAccount(addr) } // check the source account balance