Skip to content

Commit

Permalink
Merge pull request #5098 from onflow/gregor/evm/account-get-balance
Browse files Browse the repository at this point in the history
[EVM] Bridged account balance function
  • Loading branch information
sideninja authored Dec 11, 2023
2 parents 3048504 + 074d623 commit a8b45d6
Show file tree
Hide file tree
Showing 3 changed files with 168 additions and 0 deletions.
16 changes: 16 additions & 0 deletions fvm/evm/stdlib/contract.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ contract EVM {
to: self.bytes
)
}

/// Balance of the address
access(all)
fun balance(): Balance {
let balance = InternalEVM.balance(
address: self.bytes
)

return Balance(flow: balance)
}
}

access(all)
Expand Down Expand Up @@ -62,6 +72,12 @@ contract EVM {
return EVMAddress(bytes: self.addressBytes)
}

/// Get balance of the bridged account
access(all)
fun balance(): Balance {
return self.address().balance()
}

/// Deposits the given vault into the bridged account's balance
access(all)
fun deposit(from: @FlowToken.Vault) {
Expand Down
49 changes: 49 additions & 0 deletions fvm/evm/stdlib/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,48 @@ func newInternalEVMTypeDepositFunction(
)
}

const internalEVMTypeBalanceFunctionName = "balance"

var internalEVMTypeBalanceFunctionType = &sema.FunctionType{
Parameters: []sema.Parameter{
{
Label: "address",
TypeAnnotation: sema.NewTypeAnnotation(evmAddressBytesType),
},
},
ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.UFix64Type),
}

// newInternalEVMTypeBalanceFunction returns the Flow balance of the account
func newInternalEVMTypeBalanceFunction(
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.UFix64Value(account.Balance())
},
)
}

const internalEVMTypeWithdrawFunctionName = "withdraw"

var internalEVMTypeWithdrawFunctionType = &sema.FunctionType{
Expand Down Expand Up @@ -538,6 +580,7 @@ func NewInternalEVMContractValue(
internalEVMTypeDepositFunctionName: newInternalEVMTypeDepositFunction(gauge, handler),
internalEVMTypeWithdrawFunctionName: newInternalEVMTypeWithdrawFunction(gauge, handler),
internalEVMTypeDeployFunctionName: newInternalEVMTypeDeployFunction(gauge, handler),
internalEVMTypeBalanceFunctionName: newInternalEVMTypeBalanceFunction(gauge, handler),
},
nil,
nil,
Expand Down Expand Up @@ -590,6 +633,12 @@ var InternalEVMContractType = func() *sema.CompositeType {
internalEVMTypeDeployFunctionType,
"",
),
sema.NewUnmeteredPublicFunctionMember(
ty,
internalEVMTypeBalanceFunctionName,
internalEVMTypeBalanceFunctionType,
"",
),
})
return ty
}()
Expand Down
103 changes: 103 additions & 0 deletions fvm/evm/stdlib/contract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1143,3 +1143,106 @@ func TestBridgedAccountDeploy(t *testing.T) {

require.True(t, deployed)
}

func TestEVMAccountBalance(t *testing.T) {

t.Parallel()

contractsAddress := flow.BytesToAddress([]byte{0x1})

expectedBalanceValue, err := cadence.NewUFix64FromParts(1, 1337000)
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 {
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,
balance: func() types.Balance {
return types.Balance(expectedBalanceValue)
},
}
},
}

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 bridgedAccount <- EVM.createBridgedAccount()
let balance = bridgedAccount.balance()
destroy bridgedAccount
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,
transactionEnvironment,
nextTransactionLocation,
)

// Run script

actual, err := rt.ExecuteScript(
runtime.Script{
Source: script,
},
runtime.Context{
Interface: runtimeInterface,
Environment: scriptEnvironment,
Location: nextScriptLocation(),
},
)
require.NoError(t, err)

require.NoError(t, err)
require.Equal(t, expectedBalance, actual)
}

0 comments on commit a8b45d6

Please sign in to comment.