Skip to content

Commit

Permalink
Merge pull request #5303 from onflow/ramtin/4961-evm-update-balance-i…
Browse files Browse the repository at this point in the history
…n-cadence-smart-contract

[Flow EVM] update stdlib balance to use UInt instead of UFix64
  • Loading branch information
ramtinms authored Feb 1, 2024
2 parents f1f4ec5 + 67f91cb commit 443ba43
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 61 deletions.
15 changes: 8 additions & 7 deletions fvm/evm/evm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand All @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down
53 changes: 38 additions & 15 deletions fvm/evm/stdlib/contract.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
Expand All @@ -100,7 +123,7 @@ contract EVM {
from: self.addressBytes,
code: code,
gasLimit: gasLimit,
value: value.flow
value: value.attoflow
)
return EVMAddress(bytes: addressBytes)
}
Expand All @@ -119,7 +142,7 @@ contract EVM {
to: to.bytes,
data: data,
gasLimit: gasLimit,
value: value.flow
value: value.attoflow
)
}
}
Expand Down
116 changes: 96 additions & 20 deletions fvm/evm/stdlib/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()}
},
)
}
Expand All @@ -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),
Expand Down Expand Up @@ -1321,24 +1316,26 @@ 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

const isAuthorized = true
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(
Expand Down Expand Up @@ -1379,7 +1376,7 @@ var internalEVMTypeDeployFunctionType = &sema.FunctionType{
},
{
Label: "value",
TypeAnnotation: sema.NewTypeAnnotation(sema.UFix64Type),
TypeAnnotation: sema.NewTypeAnnotation(sema.UIntType),
},
},
ReturnTypeAnnotation: sema.NewTypeAnnotation(evmAddressBytesType),
Expand Down Expand Up @@ -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

Expand All @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -1521,6 +1585,18 @@ var InternalEVMContractType = func() *sema.CompositeType {
internalEVMTypeDeployFunctionType,
"",
),
sema.NewUnmeteredPublicFunctionMember(
ty,
internalEVMTypeCastToAttoFLOWFunctionName,
internalEVMTypeCastToAttoFLOWFunctionType,
"",
),
sema.NewUnmeteredPublicFunctionMember(
ty,
internalEVMTypeCastToFLOWFunctionName,
internalEVMTypeCastToFLOWFunctionType,
"",
),
sema.NewUnmeteredPublicFunctionMember(
ty,
internalEVMTypeBalanceFunctionName,
Expand Down Expand Up @@ -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,
Expand Down
Loading

0 comments on commit 443ba43

Please sign in to comment.