Skip to content

Commit 31a091f

Browse files
authored
Adds full balance script (#189)
* add script to get combined Cadence and COA balance * change to return zero balance if not successful and return all balances * use identifier * add decimals and use UFix64 for Cadence balance
1 parent 039c5c0 commit 31a091f

File tree

4 files changed

+166
-7
lines changed

4 files changed

+166
-7
lines changed
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import "EVM"
2+
import "FungibleToken"
3+
import "FlowEVMBridgeUtils"
4+
import "FlowEVMBridgeConfig"
5+
import "FungibleTokenMetadataViews"
6+
7+
/// Returns the balance of the owner of a given Fungible Token
8+
/// from their Cadence account and their COA
9+
/// Accepts multiple optional arguments, so the caller can query
10+
/// the token by its EVM ERC20 address or by its Cadence contract address and name
11+
///
12+
/// @param owner: The Flow address of the owner
13+
/// @param contractAddressArg: The optional address of the FT contract in Cadence
14+
/// @param contractNameArg: The optional name of the FT contract in Cadence
15+
/// @param erc20AddressHex: The optional ERC20 address of the FT to query
16+
///
17+
/// @return An array that contains the balance information for the user's accounts
18+
/// in this order:
19+
/// decimals (UInt256), cadence Balance (UFix64), EVM Balance (UInt256), Total Balance (UInt256)
20+
///
21+
22+
access(all) fun main(
23+
owner: Address,
24+
vaultIdentifier: String?,
25+
erc20AddressHexArg: String?
26+
): [AnyStruct] {
27+
pre {
28+
vaultIdentifier == nil ? erc20AddressHexArg != nil : true:
29+
"If the Cadence contract information is not provided, the ERC20 contract address must be provided."
30+
}
31+
32+
var typeIdentifier: String = ""
33+
var compType: Type? = nil
34+
var contractAddress: Address? = nil
35+
var contractName: String? = nil
36+
var tokenEVMAddress: String? = nil
37+
var cadenceBalance: UFix64 = 0.0
38+
var cadenceBalanceUInt256: UInt256 = 0
39+
var coaBalance: UInt256 = 0
40+
var decimals: UInt8 = 0
41+
42+
// If the caller provided the Cadence information,
43+
// Construct the composite type
44+
if vaultIdentifier != nil {
45+
typeIdentifier = vaultIdentifier!
46+
compType = CompositeType(typeIdentifier)
47+
?? panic("Could not construct Cadence type with \(typeIdentifier)")
48+
49+
// Get the EVM address of the bridged version of the Cadence FT contract
50+
if let evmAddress = FlowEVMBridgeConfig.getEVMAddressAssociated(with: compType!) {
51+
tokenEVMAddress = evmAddress.toString()
52+
}
53+
} else {
54+
// If the caller provided the EVM information,
55+
// get the Cadence type from the bridge
56+
// If getting the Cadence type doesn't work, then we'll just return the EVM balance
57+
tokenEVMAddress = erc20AddressHexArg!
58+
let address = EVM.addressFromString(tokenEVMAddress!)
59+
compType = FlowEVMBridgeConfig.getTypeAssociated(with: address)
60+
}
61+
62+
// Parse the FT identifier into its components if necessary
63+
if compType != nil {
64+
contractAddress = FlowEVMBridgeUtils.getContractAddress(fromType: compType!)
65+
contractName = FlowEVMBridgeUtils.getContractName(fromType: compType!)
66+
}
67+
68+
if let address = contractAddress {
69+
// Borrow a reference to the FT contract
70+
let resolverRef = getAccount(address)
71+
.contracts.borrow<&{FungibleToken}>(name: contractName!)
72+
?? panic("Could not borrow FungibleToken reference to the contract. Make sure the provided contract name ("
73+
.concat(contractName!).concat(") and address (").concat(address.toString()).concat(") are correct!"))
74+
75+
// Use that reference to retrieve the FTView
76+
let vaultData = resolverRef.resolveContractView(resourceType: nil, viewType: Type<FungibleTokenMetadataViews.FTVaultData>()) as! FungibleTokenMetadataViews.FTVaultData?
77+
?? panic("Could not resolve FTVaultData view. The ".concat(contractName!)
78+
.concat(" contract needs to implement the FTVaultData Metadata view in order to execute this transaction."))
79+
80+
// Get the Cadence balance of the token
81+
cadenceBalance = getAccount(owner).capabilities.borrow<&{FungibleToken.Balance}>(
82+
vaultData.metadataPath
83+
)?.balance
84+
?? 0.0
85+
}
86+
87+
// Get the COA from the owner's account
88+
if let coa = getAuthAccount<auth(BorrowValue) &Account>(owner)
89+
.storage.borrow<auth(EVM.Call) &EVM.CadenceOwnedAccount>(
90+
from: /storage/evm
91+
)
92+
{
93+
if let erc20Address = tokenEVMAddress {
94+
// Get the COA address
95+
let coaAddress = coa.address().toString()
96+
97+
// Get the ERC20 balance of the COA
98+
coaBalance = FlowEVMBridgeUtils.balanceOf(
99+
owner: EVM.addressFromString(coaAddress),
100+
evmContractAddress: EVM.addressFromString(erc20Address)
101+
)
102+
103+
if cadenceBalance != 0.0 {
104+
// Convert the Cadence balance to UInt256
105+
cadenceBalanceUInt256 = FlowEVMBridgeUtils.convertCadenceAmountToERC20Amount(
106+
cadenceBalance,
107+
erc20Address: EVM.addressFromString(erc20Address)
108+
)
109+
}
110+
111+
// Get the token decimals of the ERC20 contract
112+
decimals = FlowEVMBridgeUtils.getTokenDecimals(
113+
evmContractAddress: EVM.addressFromString(erc20Address)
114+
)
115+
}
116+
}
117+
118+
let balances = [decimals, cadenceBalance, coaBalance, cadenceBalanceUInt256+coaBalance]
119+
120+
return balances
121+
}

cadence/tests/flow_evm_bridge_tests.cdc

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1024,6 +1024,17 @@ fun testBridgeCadenceNativeTokenToEVMSucceeds() {
10241024
?? panic("Problem getting ExampleToken balance")
10251025
Test.assert(cadenceBalance == exampleTokenMintAmount)
10261026

1027+
let associatedEVMAddressHex = getAssociatedEVMAddressHex(with: exampleTokenIdentifier)
1028+
Test.assertEqual(40, associatedEVMAddressHex.length)
1029+
1030+
let decimals = getTokenDecimals(erc20AddressHex: associatedEVMAddressHex)
1031+
let expectedTotalBalance = ufix64ToUInt256(exampleTokenMintAmount, decimals: decimals)
1032+
1033+
var completeBalances = getFullBalance(ownerAddr: alice.address, vaultIdentifier: exampleTokenIdentifier, erc20AddressHex: nil)
1034+
Test.assert(completeBalances[1] as! UFix64 == exampleTokenMintAmount)
1035+
Test.assert(completeBalances[2] as! UInt256 == 0)
1036+
Test.assert(completeBalances[3] as! UInt256 == expectedTotalBalance)
1037+
10271038
var aliceCOAAddressHex = getCOAAddressHex(atFlowAddress: alice.address)
10281039

10291040
// Execute bridge to EVM
@@ -1034,26 +1045,34 @@ fun testBridgeCadenceNativeTokenToEVMSucceeds() {
10341045
beFailed: false
10351046
)
10361047

1037-
let associatedEVMAddressHex = getAssociatedEVMAddressHex(with: exampleTokenIdentifier)
1038-
Test.assertEqual(40, associatedEVMAddressHex.length)
1039-
10401048
// Confirm Alice's token balance is now 0.0
10411049
cadenceBalance = getBalance(ownerAddr: alice.address, storagePathIdentifier: "exampleTokenVault")
10421050
?? panic("Problem getting ExampleToken balance")
10431051
Test.assertEqual(0.0, cadenceBalance)
10441052

10451053
// Confirm balance on EVM side has been updated
1046-
let decimals = getTokenDecimals(erc20AddressHex: associatedEVMAddressHex)
1047-
let expectedEVMBalance = ufix64ToUInt256(exampleTokenMintAmount, decimals: decimals)
10481054
let evmBalance = balanceOf(evmAddressHex: aliceCOAAddressHex, erc20AddressHex: associatedEVMAddressHex)
1049-
Test.assertEqual(expectedEVMBalance, evmBalance)
1055+
Test.assertEqual(expectedTotalBalance, evmBalance)
10501056

10511057
// Confirm the token is locked
10521058
let lockedBalance = getLockedTokenBalance(vaultTypeIdentifier: exampleTokenIdentifier) ?? panic("Problem getting locked balance")
10531059
Test.assertEqual(exampleTokenMintAmount, lockedBalance)
10541060

10551061
let metadata = resolveLockedTokenView(bridgeAddress: bridgeAccount.address, vaultTypeIdentifier: exampleTokenIdentifier, viewIdentifier: Type<FungibleTokenMetadataViews.FTDisplay>().identifier)
10561062
Test.assert(metadata != nil, message: "Expected Vault metadata to be resolved from escrow but none was returned")
1063+
1064+
// Confirm complete balance is still the same,
1065+
// this time querying with the erc20 address
1066+
completeBalances = getFullBalance(ownerAddr: alice.address, vaultIdentifier: nil, erc20AddressHex: associatedEVMAddressHex)
1067+
Test.assert(completeBalances[1] as! UFix64 == 0.0)
1068+
Test.assert(completeBalances[2] as! UInt256 == expectedTotalBalance)
1069+
Test.assert(completeBalances[3] as! UInt256 == expectedTotalBalance)
1070+
1071+
// Query an account without the token to make sure balances are zero
1072+
completeBalances = getFullBalance(ownerAddr: bob.address, vaultIdentifier: nil, erc20AddressHex: associatedEVMAddressHex)
1073+
Test.assert(completeBalances[1] as! UFix64 == 0.0)
1074+
Test.assert(completeBalances[2] as! UInt256 == 0)
1075+
Test.assert(completeBalances[3] as! UInt256 == 0)
10571076
}
10581077

10591078
access(all)

cadence/tests/test_helpers.cdc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,20 @@ fun getBalance(ownerAddr: Address, storagePathIdentifier: String): UFix64? {
345345
return balanceResult.returnValue as! UFix64?
346346
}
347347

348+
access(all)
349+
fun getFullBalance(
350+
ownerAddr: Address,
351+
vaultIdentifier: String?,
352+
erc20AddressHex: String?
353+
): [AnyStruct] {
354+
let balanceResult = _executeScript(
355+
"../scripts/tokens/get_full_cadence_evm_balance.cdc",
356+
[ownerAddr, vaultIdentifier, erc20AddressHex]
357+
)
358+
Test.expect(balanceResult, Test.beSucceeded())
359+
return balanceResult.returnValue as! [AnyStruct]
360+
}
361+
348362
access(all)
349363
fun balanceOf(evmAddressHex: String, erc20AddressHex: String): UInt256 {
350364
let balanceOfResult = _executeScript(

flow.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@
334334
},
335335
"FungibleToken": {
336336
"source": "mainnet://f233dcee88fe0abe.FungibleToken",
337-
"hash": "050328d01c6cde307fbe14960632666848d9b7ea4fef03ca8c0bbfb0f2884068",
337+
"hash": "23c1159cf99b2b039b6b868d782d57ae39b8d784045d81597f100a4782f0285b",
338338
"aliases": {
339339
"emulator": "ee82856bf20e2aa6",
340340
"mainnet": "f233dcee88fe0abe",
@@ -433,6 +433,11 @@
433433
}
434434
},
435435
"deployments": {
436+
"emulator": {
437+
"emulator-account": [
438+
"CrossVMMetadataViews"
439+
]
440+
},
436441
"mainnet": {
437442
"mainnet-flow-evm-bridge": [
438443
"FlowEVMBridgeHandlerInterfaces",

0 commit comments

Comments
 (0)