Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[EVM] Bridged account balance function #5098

Merged
merged 18 commits into from
Dec 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
}
Loading