diff --git a/fvm/evm/evm_test.go b/fvm/evm/evm_test.go index 32f4823df20..e1c1a4c1c7a 100644 --- a/fvm/evm/evm_test.go +++ b/fvm/evm/evm_test.go @@ -12,7 +12,6 @@ import ( "github.com/onflow/flow-go/engine/execution/testutil" "github.com/onflow/flow-go/fvm" "github.com/onflow/flow-go/fvm/evm/stdlib" - "github.com/onflow/flow-go/fvm/evm/testutils" . "github.com/onflow/flow-go/fvm/evm/testutils" "github.com/onflow/flow-go/fvm/storage/snapshot" "github.com/onflow/flow-go/fvm/systemcontracts" @@ -25,7 +24,7 @@ func TestEVMRun(t *testing.T) { t.Parallel() t.Run("testing EVM.run (happy case)", func(t *testing.T) { - RunWithTestBackend(t, func(backend *testutils.TestBackend) { + RunWithTestBackend(t, func(backend *TestBackend) { RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { tc := GetStorageTestContract(t) RunWithDeployedContract(t, tc, backend, rootAddr, func(testContract *TestContract) { @@ -118,7 +117,7 @@ func TestEVMAddressDeposit(t *testing.T) { t.Parallel() - RunWithTestBackend(t, func(backend *testutils.TestBackend) { + RunWithTestBackend(t, func(backend *TestBackend) { RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { tc := GetStorageTestContract(t) RunWithDeployedContract(t, tc, backend, rootAddr, func(testContract *TestContract) { @@ -173,7 +172,7 @@ func TestBridgedAccountWithdraw(t *testing.T) { t.Parallel() - RunWithTestBackend(t, func(backend *testutils.TestBackend) { + RunWithTestBackend(t, func(backend *TestBackend) { RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { tc := GetStorageTestContract(t) RunWithDeployedContract(t, tc, backend, rootAddr, func(testContract *TestContract) { @@ -199,7 +198,9 @@ func TestBridgedAccountWithdraw(t *testing.T) { let bridgedAccount <- EVM.createBridgedAccount() bridgedAccount.deposit(from: <-vault) - let vault2 <- bridgedAccount.withdraw(balance: EVM.Balance(flow: 1.23)) + let bal = EVM.Balance(0) + bal.setFLOW(flow: 1.23) + let vault2 <- bridgedAccount.withdraw(balance: bal) let balance = vault2.balance destroy bridgedAccount destroy vault2 @@ -232,7 +233,7 @@ func TestBridgedAccountWithdraw(t *testing.T) { func TestBridgedAccountDeploy(t *testing.T) { t.Parallel() - RunWithTestBackend(t, func(backend *testutils.TestBackend) { + RunWithTestBackend(t, func(backend *TestBackend) { RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { tc := GetStorageTestContract(t) RunWithDeployedContract(t, tc, backend, rootAddr, func(testContract *TestContract) { @@ -261,7 +262,7 @@ func TestBridgedAccountDeploy(t *testing.T) { let address = bridgedAccount.deploy( code: [], gasLimit: 53000, - value: EVM.Balance(flow: 1.23) + value: EVM.Balance(attoflow: 1230000000000000000) ) destroy bridgedAccount return address.bytes diff --git a/fvm/evm/stdlib/contract.cdc b/fvm/evm/stdlib/contract.cdc index 3151542131b..8dfec9eadb3 100644 --- a/fvm/evm/stdlib/contract.cdc +++ b/fvm/evm/stdlib/contract.cdc @@ -22,28 +22,47 @@ contract EVM { let balance = InternalEVM.balance( address: self.bytes ) - - return Balance(flow: balance) + return Balance(attoflow: balance) } } access(all) struct Balance { - /// The balance in FLOW + /// The balance in atto-FLOW + /// Atto-FLOW is the smallest denomination of FLOW (1e18 FLOW) + /// that is used to store account balances inside EVM + /// similar to the way WEI is used to store ETH divisible to 18 decimal places. + access(all) + var attoflow: UInt + + /// Constructs a new balance + access(all) + init(attoflow: UInt) { + self.attoflow = attoflow + } + + /// Sets the balance by a UFix64 (8 decimal points), the format + /// that is used in Cadence to store FLOW tokens. access(all) - let flow: UFix64 + fun setFLOW(flow: UFix64){ + self.attoflow = InternalEVM.castToAttoFLOW(balance: flow) + } - /// Constructs a new balance, given the balance in FLOW - init(flow: UFix64) { - self.flow = flow + /// Casts the balance to a UFix64 (rounding down) + /// Warning! casting a balance to a UFix64 which supports a lower level of precision + /// (8 decimal points in compare to 18) might result in rounding down error. + /// Use the toAttoFlow function if you care need more accuracy. + access(all) + fun inFLOW(): UFix64 { + return InternalEVM.castToFLOW(balance: self.attoflow) } - // TODO: - // /// Returns the balance in terms of atto-FLOW. - // /// Atto-FLOW is the smallest denomination of FLOW inside EVM - // access(all) - // fun toAttoFlow(): UInt64 + /// Returns the balance in Atto-FLOW + access(all) + fun inAttoFLOW(): UInt { + return self.attoflow + } } access(all) @@ -79,11 +98,15 @@ contract EVM { } /// Withdraws the balance from the bridged account's balance + /// Note that amounts smaller than 10nF (10e-8) can't be withdrawn + /// given that Flow Token Vaults use UFix64s to store balances. + /// If the given balance conversion to UFix64 results in + /// rounding error, this function would fail. access(all) fun withdraw(balance: Balance): @FlowToken.Vault { let vault <- InternalEVM.withdraw( from: self.addressBytes, - amount: balance.flow + amount: balance.attoflow ) as! @FlowToken.Vault return <-vault } @@ -100,7 +123,7 @@ contract EVM { from: self.addressBytes, code: code, gasLimit: gasLimit, - value: value.flow + value: value.attoflow ) return EVMAddress(bytes: addressBytes) } @@ -119,7 +142,7 @@ contract EVM { to: to.bytes, data: data, gasLimit: gasLimit, - value: value.flow + value: value.attoflow ) } } diff --git a/fvm/evm/stdlib/contract.go b/fvm/evm/stdlib/contract.go index bd19dab6a80..bd07f358b8f 100644 --- a/fvm/evm/stdlib/contract.go +++ b/fvm/evm/stdlib/contract.go @@ -1019,7 +1019,7 @@ var internalEVMTypeCallFunctionType = &sema.FunctionType{ }, { Label: "value", - TypeAnnotation: sema.NewTypeAnnotation(sema.UFix64Type), + TypeAnnotation: sema.NewTypeAnnotation(sema.UIntType), }, }, ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.ByteArrayType), @@ -1120,12 +1120,12 @@ func newInternalEVMTypeCallFunction( // Get balance - balanceValue, ok := invocation.Arguments[4].(interpreter.UFix64Value) + balanceValue, ok := invocation.Arguments[4].(interpreter.UIntValue) if !ok { panic(errors.NewUnreachableError()) } - balance := types.NewBalanceFromUFix64(cadence.UFix64(balanceValue)) + balance := types.NewBalance(balanceValue.BigInt) // Call const isAuthorized = true @@ -1242,7 +1242,7 @@ var internalEVMTypeBalanceFunctionType = &sema.FunctionType{ TypeAnnotation: sema.NewTypeAnnotation(evmAddressBytesType), }, }, - ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.UFix64Type), + ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.UIntType), } // newInternalEVMTypeBalanceFunction returns the Flow balance of the account @@ -1270,12 +1270,7 @@ func newInternalEVMTypeBalanceFunction( const isAuthorized = false account := handler.AccountByAddress(address, isAuthorized) - // TODO: return roundoff flag or handle it - ufix, _, err := types.ConvertBalanceToUFix64(account.Balance()) - if err != nil { - panic(err) - } - return interpreter.UFix64Value(ufix) + return interpreter.UIntValue{BigInt: account.Balance()} }, ) } @@ -1290,7 +1285,7 @@ var internalEVMTypeWithdrawFunctionType = &sema.FunctionType{ }, { Label: "amount", - TypeAnnotation: sema.NewTypeAnnotation(sema.UFix64Type), + TypeAnnotation: sema.NewTypeAnnotation(sema.UIntType), }, }, ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.AnyResourceType), @@ -1321,12 +1316,12 @@ func newInternalEVMTypeWithdrawFunction( // Get amount - amountValue, ok := invocation.Arguments[1].(interpreter.UFix64Value) + amountValue, ok := invocation.Arguments[1].(interpreter.UIntValue) if !ok { panic(errors.NewUnreachableError()) } - amount := types.NewBalanceFromUFix64(cadence.UFix64(amountValue)) + amount := types.NewBalance(amountValue.BigInt) // Withdraw @@ -1334,11 +1329,13 @@ func newInternalEVMTypeWithdrawFunction( account := handler.AccountByAddress(fromAddress, isAuthorized) vault := account.Withdraw(amount) - // TODO: return rounded off flag or handle it ? - ufix, _, err := types.ConvertBalanceToUFix64(vault.Balance()) + ufix, roundedOff, err := types.ConvertBalanceToUFix64(vault.Balance()) if err != nil { panic(err) } + if roundedOff { + panic(types.ErrWithdrawBalanceRounding) + } // TODO: improve: maybe call actual constructor return interpreter.NewCompositeValue( @@ -1379,7 +1376,7 @@ var internalEVMTypeDeployFunctionType = &sema.FunctionType{ }, { Label: "value", - TypeAnnotation: sema.NewTypeAnnotation(sema.UFix64Type), + TypeAnnotation: sema.NewTypeAnnotation(sema.UIntType), }, }, ReturnTypeAnnotation: sema.NewTypeAnnotation(evmAddressBytesType), @@ -1431,12 +1428,12 @@ func newInternalEVMTypeDeployFunction( // Get value - amountValue, ok := invocation.Arguments[3].(interpreter.UFix64Value) + amountValue, ok := invocation.Arguments[3].(interpreter.UIntValue) if !ok { panic(errors.NewUnreachableError()) } - amount := types.NewBalanceFromUFix64(cadence.UFix64(amountValue)) + amount := types.NewBalance(amountValue.BigInt) // Deploy @@ -1449,6 +1446,71 @@ func newInternalEVMTypeDeployFunction( ) } +const internalEVMTypeCastToAttoFLOWFunctionName = "castToAttoFLOW" + +var internalEVMTypeCastToAttoFLOWFunctionType = &sema.FunctionType{ + Parameters: []sema.Parameter{ + { + Label: "balance", + TypeAnnotation: sema.NewTypeAnnotation(sema.UFix64Type), + }, + }, + ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.UIntType), +} + +func newInternalEVMTypeCastToAttoFLOWFunction( + gauge common.MemoryGauge, + handler types.ContractHandler, +) *interpreter.HostFunctionValue { + return interpreter.NewHostFunctionValue( + gauge, + internalEVMTypeCallFunctionType, + func(invocation interpreter.Invocation) interpreter.Value { + balanceValue, ok := invocation.Arguments[0].(interpreter.UFix64Value) + if !ok { + panic(errors.NewUnreachableError()) + } + balance := types.NewBalanceFromUFix64(cadence.UFix64(balanceValue)) + return interpreter.UIntValue{BigInt: balance} + }, + ) +} + +const internalEVMTypeCastToFLOWFunctionName = "castToFLOW" + +var internalEVMTypeCastToFLOWFunctionType = &sema.FunctionType{ + Parameters: []sema.Parameter{ + { + Label: "balance", + TypeAnnotation: sema.NewTypeAnnotation(sema.UIntType), + }, + }, + ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.UFix64Type), +} + +func newInternalEVMTypeCastToFLOWFunction( + gauge common.MemoryGauge, + handler types.ContractHandler, +) *interpreter.HostFunctionValue { + return interpreter.NewHostFunctionValue( + gauge, + internalEVMTypeCallFunctionType, + func(invocation interpreter.Invocation) interpreter.Value { + balanceValue, ok := invocation.Arguments[0].(interpreter.UIntValue) + if !ok { + panic(errors.NewUnreachableError()) + } + balance := types.NewBalance(balanceValue.BigInt) + // ignoring the rounding error and let user handle it + v, _, err := types.ConvertBalanceToUFix64(balance) + if err != nil { + panic(err) + } + return interpreter.UFix64Value(v) + }, + ) +} + func NewInternalEVMContractValue( gauge common.MemoryGauge, handler types.ContractHandler, @@ -1469,6 +1531,8 @@ func NewInternalEVMContractValue( internalEVMTypeBalanceFunctionName: newInternalEVMTypeBalanceFunction(gauge, handler), internalEVMTypeEncodeABIFunctionName: newInternalEVMTypeEncodeABIFunction(gauge, location), internalEVMTypeDecodeABIFunctionName: newInternalEVMTypeDecodeABIFunction(gauge, location), + internalEVMTypeCastToAttoFLOWFunctionName: newInternalEVMTypeCastToAttoFLOWFunction(gauge, handler), + internalEVMTypeCastToFLOWFunctionName: newInternalEVMTypeCastToFLOWFunction(gauge, handler), }, nil, nil, @@ -1521,6 +1585,18 @@ var InternalEVMContractType = func() *sema.CompositeType { internalEVMTypeDeployFunctionType, "", ), + sema.NewUnmeteredPublicFunctionMember( + ty, + internalEVMTypeCastToAttoFLOWFunctionName, + internalEVMTypeCastToAttoFLOWFunctionType, + "", + ), + sema.NewUnmeteredPublicFunctionMember( + ty, + internalEVMTypeCastToFLOWFunctionName, + internalEVMTypeCastToFLOWFunctionType, + "", + ), sema.NewUnmeteredPublicFunctionMember( ty, internalEVMTypeBalanceFunctionName, @@ -1600,8 +1676,8 @@ func NewBalanceCadenceType(address common.Address) *cadence.StructType { "EVM.Balance", []cadence.Field{ { - Identifier: "flow", - Type: cadence.UFix64Type{}, + Identifier: "attoflow", + Type: cadence.UIntType{}, }, }, nil, diff --git a/fvm/evm/stdlib/contract_test.go b/fvm/evm/stdlib/contract_test.go index 7ad8cc64adb..c93468f6d08 100644 --- a/fvm/evm/stdlib/contract_test.go +++ b/fvm/evm/stdlib/contract_test.go @@ -1581,9 +1581,9 @@ func TestEVMEncodeDecodeABIErrors(t *testing.T) { access(all) struct Token { access(all) let id: Int - access(all) var balance: Int + access(all) var balance: UInt - init(id: Int, balance: Int) { + init(id: Int, balance: UInt) { self.id = id self.balance = balance } @@ -2113,9 +2113,9 @@ func TestEVMEncodeDecodeABIErrors(t *testing.T) { access(all) struct Token { access(all) let id: Int - access(all) var balance: Int + access(all) var balance: UInt - init(id: Int, balance: Int) { + init(id: Int, balance: UInt) { self.id = id self.balance = balance } @@ -2644,8 +2644,8 @@ func TestBalanceConstructionAndReturn(t *testing.T) { import EVM from 0x1 access(all) - fun main(_ flow: UFix64): EVM.Balance { - return EVM.Balance(flow: flow) + fun main(_ attoflow: UInt): EVM.Balance { + return EVM.Balance(attoflow: attoflow) } `) @@ -2692,8 +2692,7 @@ func TestBalanceConstructionAndReturn(t *testing.T) { // Run script - flowValue, err := cadence.NewUFix64FromParts(1, 23000000) - require.NoError(t, err) + flowValue := cadence.NewUInt(1230000000000000000) result, err := rt.ExecuteScript( runtime.Script{ @@ -2986,13 +2985,15 @@ func TestBridgedAccountCall(t *testing.T) { access(all) fun main(): [UInt8] { let bridgedAccount <- EVM.createBridgedAccount() + let bal = EVM.Balance(0) + bal.setFLOW(flow: 1.23) let response = bridgedAccount.call( to: EVM.EVMAddress( bytes: [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] ), data: [4, 5, 6], gasLimit: 9999, - value: EVM.Balance(flow: 1.23) + value: bal ) destroy bridgedAccount return response @@ -3237,7 +3238,7 @@ func TestBridgedAccountWithdraw(t *testing.T) { let bridgedAccount <- EVM.createBridgedAccount() bridgedAccount.deposit(from: <-vault) - let vault2 <- bridgedAccount.withdraw(balance: EVM.Balance(flow: 1.23)) + let vault2 <- bridgedAccount.withdraw(balance: EVM.Balance(attoflow: 1230000000000000000)) let balance = vault2.balance destroy bridgedAccount destroy vault2 @@ -3353,7 +3354,7 @@ func TestBridgedAccountDeploy(t *testing.T) { let address = bridgedAccount.deploy( code: [4, 5, 6], gasLimit: 9999, - value: EVM.Balance(flow: 1.23) + value: EVM.Balance(flow: 1230000000000000000) ) destroy bridgedAccount return address.bytes @@ -3442,13 +3443,11 @@ func TestEVMAccountBalance(t *testing.T) { contractsAddress := flow.BytesToAddress([]byte{0x1}) - expectedBalanceValue, err := cadence.NewUFix64FromParts(1, 1337000) + expectedBalanceValue := cadence.NewUInt(1013370000000000000) expectedBalance := cadence. NewStruct([]cadence.Value{expectedBalanceValue}). WithType(stdlib.NewBalanceCadenceType(common.Address(contractsAddress))) - require.NoError(t, err) - handler := &testContractHandler{ flowTokenAddress: common.Address(contractsAddress), accountByAddress: func(fromAddress types.Address, isAuthorized bool) types.Account { @@ -3458,7 +3457,7 @@ func TestEVMAccountBalance(t *testing.T) { return &testFlowAccount{ address: fromAddress, balance: func() types.Balance { - return types.NewBalanceFromUFix64(expectedBalanceValue) + return types.NewBalance(expectedBalanceValue.Value) }, } }, @@ -3537,7 +3536,7 @@ func TestEVMAccountBalance(t *testing.T) { require.NoError(t, err) require.NoError(t, err) - require.Equal(t, expectedBalance, actual) + require.Equal(t, expectedBalance.ToGoValue(), actual.ToGoValue()) } func TestEVMAccountBalanceForABIOnlyContract(t *testing.T) { diff --git a/fvm/evm/types/balance.go b/fvm/evm/types/balance.go index 1de293ae851..a586ef71170 100644 --- a/fvm/evm/types/balance.go +++ b/fvm/evm/types/balance.go @@ -21,10 +21,10 @@ var ( ) // Balance represents the balance of an address -// in the evm environment (Flow EVM), balances are kept in attoflow (1e10^-18 flow); +// in the evm environment (Flow EVM), balances are kept in attoflow (1e-18 flow); // the smallest denomination of FLOW token (similar to how Wei is used to store Eth) // But A Cadence FLOW Vault uses a Cadence.UFix64 to store values in Flow, which means -// 1e18^-8 is the smallest value that can be stored on the vault. +// 1e-8 is the smallest value that can be stored on the vault. // The balance here considers the highest precision (attoflow) but utility // function has been provided for conversion from/to UFix64 to prevent accidental // conversion errors and dealing with rounding errors. diff --git a/fvm/fvm_test.go b/fvm/fvm_test.go index f466a7fcf43..5b00ed26470 100644 --- a/fvm/fvm_test.go +++ b/fvm/fvm_test.go @@ -3086,7 +3086,7 @@ func TestEVM(t *testing.T) { import EVM from %s pub fun main() { - let bal = EVM.Balance(flow: 1.0); + let bal = EVM.Balance(attoflow: 1000000000000000000); let acc <- EVM.createBridgedAccount(); // withdraw insufficient balance destroy acc.withdraw(balance: bal);