From 99ec218fcf95b0fd2524c3151974e3340bde99bb Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Tue, 23 Apr 2024 17:43:21 -0500 Subject: [PATCH] add supporting txns, scripts & test coverage for TokenHandler integration --- .../example-assets/ExampleHandledToken.cdc | 2 +- .../tests/flow_evm_bridge_handler_tests.cdc | 374 +++++++++++++----- cadence/tests/flow_evm_bridge_tests.cdc | 29 +- cadence/tests/test_helpers.cdc | 36 +- .../bridge/admin/enable_token_handler.cdc | 26 ++ .../admin/set_handler_target_evm_address.cdc | 7 +- 6 files changed, 352 insertions(+), 122 deletions(-) create mode 100644 cadence/transactions/bridge/admin/enable_token_handler.cdc diff --git a/cadence/contracts/example-assets/ExampleHandledToken.cdc b/cadence/contracts/example-assets/ExampleHandledToken.cdc index e5651d15..bcdd6bb4 100644 --- a/cadence/contracts/example-assets/ExampleHandledToken.cdc +++ b/cadence/contracts/example-assets/ExampleHandledToken.cdc @@ -207,7 +207,7 @@ access(all) contract ExampleHandledToken: FungibleToken { } init() { - self.totalSupply = 1000.0 + self.totalSupply = 0.0 self.VaultStoragePath = /storage/exampleTokenVault self.VaultPublicPath = /public/exampleTokenVault diff --git a/cadence/tests/flow_evm_bridge_handler_tests.cdc b/cadence/tests/flow_evm_bridge_handler_tests.cdc index 416696ad..c81c3d33 100644 --- a/cadence/tests/flow_evm_bridge_handler_tests.cdc +++ b/cadence/tests/flow_evm_bridge_handler_tests.cdc @@ -227,20 +227,34 @@ fun setup() { arguments: [] ) Test.expect(err, Test.beNil()) + + // Set bridge fees + let updateOnboardFeeResult = executeTransaction( + "../transactions/bridge/admin/update_onboard_fee.cdc", + [expectedOnboardFee], + bridgeAccount + ) + Test.expect(updateOnboardFeeResult, Test.beSucceeded()) + let updateBaseFeeResult = executeTransaction( + "../transactions/bridge/admin/update_base_fee.cdc", + [expectedBaseFee], + bridgeAccount + ) + Test.expect(updateBaseFeeResult, Test.beSucceeded()) } /* --- ASSET & ACCOUNT SETUP - Configure test accounts with assets to bridge --- */ +// Create a COA in Alice's account who will be the test asset owner for both Cadence & ERC20 FTs access(all) fun testCreateCOASucceeds() { transferFlow(signer: serviceAccount, recipient: alice.address, amount: 1_000.0) createCOA(signer: alice, fundingAmount: 100.0) let coaAddressHex = getCOAAddressHex(atFlowAddress: alice.address) - Test.assertEqual(40, coaAddressHex.length) } -// Mint tokens to put some in circulation +// Mint tokens to put some in circulation in Cadence access(all) fun testMintExampleTokenSucceeds() { let setupVaultResult = executeTransaction( @@ -268,6 +282,7 @@ fun testMintExampleTokenSucceeds() { } // Hand off Minter to bridge handler - bridge then has sole authority to mint based on contract logic +// Configuring the Handler also disables onboarding of the Cadence-native token to the bridge access(all) fun testConfigureCadenceNativeTokenHandlerSucceeds() { let handlerSetupTxn = Test.Transaction( @@ -282,7 +297,7 @@ fun testConfigureCadenceNativeTokenHandlerSucceeds() { // TODO: Add event validation when EVM and EVM dependent contracts can be imported to Test env } -// Mint tokens to put some in circulation +// ExampleHandledToken no longer has minter after handoff, so minting should fail access(all) fun testMintExampleTokenFails() { let aliceBalance = getBalance(ownerAddr: alice.address, storagePathIdentifier: "exampleTokenVault") @@ -297,6 +312,7 @@ fun testMintExampleTokenFails() { Test.expect(mintExampleTokenResult, Test.beFailed()) } +// ERC20 deploys successfully - this will be used as the targetEVMAddress in our TokenHandler access(all) fun testDeployERC20Succeeds() { let erc20DeployResult = executeTransaction( @@ -307,13 +323,13 @@ fun testDeployERC20Succeeds() { Test.expect(erc20DeployResult, Test.beSucceeded()) let erc20AddressHex = getDeployedAddressFromDeployer(name: "erc20") - Test.assertEqual(40, erc20AddressHex.length) } +// Set the TokenHandler's targetEVMAddress to the deployed ERC20 contract address +// This will filter requests to onboard the ERC20 to the bridge as the Cadence-nat access(all) fun testSetHandlerTargetEVMAddressSucceeds() { let erc20AddressHex = getDeployedAddressFromDeployer(name: "erc20") - Test.assertEqual(40, erc20AddressHex.length) let setHandlerTargetResult = executeTransaction( "../transactions/bridge/admin/set_handler_target_evm_address.cdc", @@ -328,18 +344,16 @@ fun testSetHandlerTargetEVMAddressSucceeds() { Test.assertEqual(erc20AddressHex, associatedEVMAddressHex) } +// Mint ERC20 tokens to bridge escrow so requests from Cadence to EVM can be fulfilled access(all) fun testMintERC20ToBridgeEscrowSucceeds() { let bridgeCOAAddressHex = getBridgeCOAAddressHex() - let exampleTokenTotalSupplyResult = executeScript( - "../scripts/tokens/total_supply.cdc", - [exampleHandledTokenAccount.address, "ExampleHandledToken", exampleTokenIdentifier] - ) - Test.expect(exampleTokenTotalSupplyResult, Test.beSucceeded()) - let exampleTokenTotalSupply = exampleTokenTotalSupplyResult.returnValue as! UFix64? - ?? panic("Problem getting total supply") + let exampleTokenTotalSupply = getCadenceTotalSupply( + contractAddress: exampleHandledTokenAccount.address, + contractName: "ExampleHandledToken", + vaultIdentifier: exampleTokenIdentifier + ) ?? panic("Problem getting total supply of Cadence tokens") let erc20AddressHex = getDeployedAddressFromDeployer(name: "erc20") - Test.assertEqual(40, erc20AddressHex.length) // Convert total supply UFix64 to UInt256 for ERC20 minting let uintTotalSupply = ufix64ToUInt256(exampleTokenTotalSupply, decimals: defaultDecimals) @@ -355,12 +369,11 @@ fun testMintERC20ToBridgeEscrowSucceeds() { Test.assertEqual(uintTotalSupply, escrowBalance) } +// Mint ERC20 tokens to Alice's COA so she can bridge them to the Cadence access(all) fun testMintERC20ToArbitraryRecipientSucceeds() { let aliceCOAAddressHex = getCOAAddressHex(atFlowAddress: alice.address) - Test.assertEqual(40, aliceCOAAddressHex.length) let erc20AddressHex = getDeployedAddressFromDeployer(name: "erc20") - Test.assertEqual(40, erc20AddressHex.length) let mintERC20Result = executeTransaction( "../transactions/example-assets/evm-assets/mint_erc20.cdc", @@ -373,75 +386,9 @@ fun testMintERC20ToArbitraryRecipientSucceeds() { Test.assertEqual(erc20MintAmount, aliceBalance) } -access(all) -fun testUpdateBridgeFeesSucceeds() { - let bytesUsed: UInt64 = 1024 - let expectedFinalFee = FlowStorageFees.storageCapacityToFlow( - FlowStorageFees.convertUInt64StorageBytesToUFix64Megabytes(bytesUsed) - ) + expectedBaseFee - - // Validate the initialized values are set to 0.0 - var actualOnboardFeeResult = executeScript( - "../scripts/config/get_onboard_fee.cdc", - [] - ) - Test.expect(actualOnboardFeeResult, Test.beSucceeded()) - var actualBaseFeeResult = executeScript( - "../scripts/config/get_base_fee.cdc", - [] - ) - Test.expect(actualBaseFeeResult, Test.beSucceeded()) - - Test.assertEqual(0.0, actualOnboardFeeResult.returnValue as! UFix64? ?? panic("Problem getting onboard fee")) - Test.assertEqual(0.0, actualBaseFeeResult.returnValue as! UFix64? ?? panic("Problem getting base fee")) - - var actualCalculatedResult = executeScript( - "../scripts/bridge/calculate_bridge_fee.cdc", - [bytesUsed] - ) - Test.expect(actualCalculatedResult, Test.beSucceeded()) - Test.assertEqual(0.0, actualCalculatedResult.returnValue as! UFix64? ?? panic("Problem getting calculated fee")) - - // Set the fees to new values - let updateOnboardFeeResult = executeTransaction( - "../transactions/bridge/admin/update_onboard_fee.cdc", - [expectedOnboardFee], - bridgeAccount - ) - Test.expect(updateOnboardFeeResult, Test.beSucceeded()) - let updateBaseFeeResult = executeTransaction( - "../transactions/bridge/admin/update_base_fee.cdc", - [expectedBaseFee], - bridgeAccount - ) - Test.expect(updateBaseFeeResult, Test.beSucceeded()) - - // Validate the values have been updated - actualOnboardFeeResult = executeScript( - "../scripts/config/get_onboard_fee.cdc", - [] - ) - Test.expect(actualOnboardFeeResult, Test.beSucceeded()) - actualBaseFeeResult = executeScript( - "../scripts/config/get_base_fee.cdc", - [] - ) - Test.expect(actualBaseFeeResult, Test.beSucceeded()) - - Test.assertEqual(expectedOnboardFee, actualOnboardFeeResult.returnValue as! UFix64? ?? panic("Problem getting onboard fee")) - Test.assertEqual(expectedBaseFee, actualBaseFeeResult.returnValue as! UFix64? ?? panic("Problem getting base fee")) - - actualCalculatedResult = executeScript( - "../scripts/bridge/calculate_bridge_fee.cdc", - [bytesUsed] - ) - Test.expect(actualCalculatedResult, Test.beSucceeded()) - Test.assertEqual(expectedFinalFee, actualCalculatedResult.returnValue as! UFix64? ?? panic("Problem getting calculated fee")) - -} - /* --- ONBOARDING - Test asset onboarding to the bridge --- */ +// Since the type has a TokenHandler, onboarding should fail access(all) fun testOnboardHandledTokenByTypeFails() { var onboaringRequiredResult = executeScript( @@ -452,6 +399,7 @@ fun testOnboardHandledTokenByTypeFails() { var requiresOnboarding = onboaringRequiredResult.returnValue as! Bool? ?? panic("Problem getting onboarding requirement") Test.assertEqual(false, requiresOnboarding) + // Should fails since request routes to TokenHandler and it's not enabled let onboardingResult = executeTransaction( "../transactions/bridge/onboarding/onboard_by_type_identifier.cdc", [exampleTokenIdentifier], @@ -460,10 +408,10 @@ fun testOnboardHandledTokenByTypeFails() { Test.expect(onboardingResult, Test.beFailed()) } +// Since the erc20 Address has a TokenHandler, onboarding should fail access(all) fun testOnboardHandledERC20ByEVMAddressFails() { let erc20AddressHex = getDeployedAddressFromDeployer(name: "erc20") - Test.assertEqual(40, erc20AddressHex.length) var onboaringRequiredResult = executeScript( "../scripts/bridge/evm_address_requires_onboarding.cdc", @@ -473,6 +421,7 @@ fun testOnboardHandledERC20ByEVMAddressFails() { var requiresOnboarding = onboaringRequiredResult.returnValue as! Bool? ?? panic("Problem getting onboarding requirement") Test.assertEqual(false, requiresOnboarding) + // Should fails since request routes to TokenHandler and it's not enabled var onboardingResult = executeTransaction( "../transactions/bridge/onboarding/onboard_by_evm_address.cdc", [erc20AddressHex], @@ -483,12 +432,253 @@ fun testOnboardHandledERC20ByEVMAddressFails() { /* --- BRIDGING FUNGIBLE TOKENS - Test bridging both Cadence- & EVM-native fungible tokens --- */ -// TODO - bridge to EVM fails -// TODO - bridge from EVM fails -// TODO - handler enable bridging succeeds -// TODO - snapshot -// TODO - bridge all funds to EVM succeeds -// TODO - bridge from EVM succeeds -// TODO - reset to snapshot -// TODO - bridge all funds from EVM succeeds -// TODO - bridge to EVM succeeds +// Bridging to EVM before TokenHandler is enabled should fail +access(all) +fun testBridgeHandledCadenceNativeTokenToEVMFails() { + var cadenceBalance = getBalance(ownerAddr: alice.address, storagePathIdentifier: "exampleTokenVault") + ?? panic("Problem getting ExampleToken balance") + Test.assert(cadenceBalance == exampleTokenMintAmount) + + var aliceCOAAddressHex = getCOAAddressHex(atFlowAddress: alice.address) + + // Execute bridge to EVM - should fail since Handler is not enabled + bridgeTokensToEVM( + signer: alice, + contractAddr: exampleHandledTokenAccount.address, + contractName: "ExampleHandledToken", + amount: cadenceBalance, + beFailed: true + ) +} + +// Bridging frrom EVM before TokenHandler is enabled should fail +access(all) +fun testBridgeHandledCadenceNativeTokenFromEVMFails() { + let erc20AddressHex = getDeployedAddressFromDeployer(name: "erc20") + let aliceCOAAddressHex = getCOAAddressHex(atFlowAddress: alice.address) + + // Confirm ownership on EVM side with Alice COA as owner of ERC721 representation + var evmBalance = balanceOf(evmAddressHex: aliceCOAAddressHex, erc20AddressHex: erc20AddressHex) + Test.assertEqual(erc20MintAmount, evmBalance) + + // Execute bridge from EVM + bridgeTokensFromEVM( + signer: alice, + contractAddr: exampleHandledTokenAccount.address, + contractName: "ExampleHandledToken", + amount: evmBalance, + beFailed: true + ) +} + +// Now enable TokenHandler to bridge in both directions +access(all) +fun testEnableTokenHandlerSucceeds() { + let enabledResult = executeTransaction( + "../transactions/bridge/admin/enable_token_handler.cdc", + [exampleTokenIdentifier], + bridgeAccount + ) + Test.expect(enabledResult, Test.beSucceeded()) + // TODO: Validate event emission and values +} + +// Validate that funds can be bridged from Cadence to EVM, resulting in balance increase in deployed ERC20 as target +access(all) +fun testBridgeHandledCadenceNativeTokenToEVMFirstSucceeds() { + snapshot = getCurrentBlockHeight() + + let erc20AddressHex = getDeployedAddressFromDeployer(name: "erc20") + let erc20TotalSupplyBefore = getEVMTotalSupply(erc20AddressHex: erc20AddressHex) + + // Alice was the only recipient, so their balance should be the total supply + var exampleTokenTotalSupply = getCadenceTotalSupply( + contractAddress: exampleHandledTokenAccount.address, + contractName: "ExampleHandledToken", + vaultIdentifier: exampleTokenIdentifier + ) ?? panic("Problem getting total supply of Cadence tokens") + + let cadenceBalance = getBalance(ownerAddr: alice.address, storagePathIdentifier: "exampleTokenVault") + ?? panic("Problem getting ExampleToken balance") + Test.assert(cadenceBalance == exampleTokenMintAmount) + Test.assert(cadenceBalance == exampleTokenTotalSupply) + let uintCadenceBalance = ufix64ToUInt256(cadenceBalance, decimals: defaultDecimals) + + let aliceCOAAddressHex = getCOAAddressHex(atFlowAddress: alice.address) + + // Execute bridge to EVM + bridgeTokensToEVM( + signer: alice, + contractAddr: exampleHandledTokenAccount.address, + contractName: "ExampleHandledToken", + amount: cadenceBalance, + beFailed: false + ) + + // Confirm ownership on EVM side with Alice COA as owner of ERC721 representation + let evmBalance = balanceOf(evmAddressHex: aliceCOAAddressHex, erc20AddressHex: erc20AddressHex) + Test.assertEqual(erc20MintAmount + uintCadenceBalance, evmBalance) // bridged balance + previously minted ERC20 + + // Validate that the originally minted tokens were burned in the process of bridging + exampleTokenTotalSupply = getCadenceTotalSupply( + contractAddress: exampleHandledTokenAccount.address, + contractName: "ExampleHandledToken", + vaultIdentifier: exampleTokenIdentifier + ) ?? panic("Problem getting total supply of Cadence tokens") + Test.assertEqual(0.0, exampleTokenTotalSupply) + + // Validate that the ERC20 balance in circulation remained the same + let erc20TotalSupplyAfter = getEVMTotalSupply(erc20AddressHex: erc20AddressHex) + Test.assertEqual(erc20TotalSupplyBefore, erc20TotalSupplyAfter) + + let escrowBalance = balanceOf(evmAddressHex: getBridgeCOAAddressHex(), erc20AddressHex: erc20AddressHex) + // Validate that there are no funds now in escrow since total Cadence circulation was bridged to EVM + Test.assertEqual(UInt256(0), escrowBalance) +} + +// With all funds now in EVM, we can test bridging back to Cadence +access(all) +fun testBridgeHandledCadenceNativeTokenFromEVMSecondSucceeds() { + let erc20AddressHex = getDeployedAddressFromDeployer(name: "erc20") + let aliceCOAAddressHex = getCOAAddressHex(atFlowAddress: alice.address) + + let erc20TotalSupplyBefore = getEVMTotalSupply(erc20AddressHex: erc20AddressHex) + + // Execute bridge from EVM, bridging Alice's full balance to Cadence + let evmBalance = balanceOf(evmAddressHex: aliceCOAAddressHex, erc20AddressHex: erc20AddressHex) + let ufixEVMbalance = uint256ToUFix64(evmBalance, decimals: defaultDecimals) + bridgeTokensFromEVM( + signer: alice, + contractAddr: exampleHandledTokenAccount.address, + contractName: "ExampleHandledToken", + amount: evmBalance, + beFailed: false + ) + + // Confirm that Alice's balance is now the total supply + let cadenceBalance = getBalance(ownerAddr: alice.address, storagePathIdentifier: "exampleTokenVault") + ?? panic("Problem getting ExampleToken balance") + Test.assertEqual(ufixEVMbalance, cadenceBalance) + + // Confirm that the ERC20 balance was burned in the process of bridging + let evmBalanceAfter = balanceOf(evmAddressHex: aliceCOAAddressHex, erc20AddressHex: erc20AddressHex) + Test.assertEqual(UInt256(0), evmBalanceAfter) + + // Validate that the ERC20 balance in circulation remained the same + let erc20TotalSupplyAfter = getEVMTotalSupply(erc20AddressHex: erc20AddressHex) + Test.assertEqual(erc20TotalSupplyBefore, erc20TotalSupplyAfter) + + // Validate that all ERC20 funds are now in escrow since all bridged to Cadence + let escrowBalance = balanceOf(evmAddressHex: getBridgeCOAAddressHex(), erc20AddressHex: erc20AddressHex) + Test.assertEqual(erc20TotalSupplyAfter, escrowBalance) +} + +// Now test bridging with liquidity flow moving entirely from EVM to Cadence and back +access(all) +fun testBridgeHandledCadenceNativeTokenFromEVMFirstSucceeds() { + // Reset to snapshot before bridging between VMs + Test.reset(to: snapshot) + + let erc20AddressHex = getDeployedAddressFromDeployer(name: "erc20") + let aliceCOAAddressHex = getCOAAddressHex(atFlowAddress: alice.address) + + var erc20TotalSupplyBefore = getEVMTotalSupply(erc20AddressHex: getDeployedAddressFromDeployer(name: "erc20")) + let cadenceTotalSupplyBefore = getCadenceTotalSupply( + contractAddress: exampleHandledTokenAccount.address, + contractName: "ExampleHandledToken", + vaultIdentifier: exampleTokenIdentifier + ) ?? panic("Problem getting total supply of Cadence tokens") + let uintCadenceTotalSupplyBefore = ufix64ToUInt256(cadenceTotalSupplyBefore, decimals: defaultDecimals) + Test.assertEqual(uintCadenceTotalSupplyBefore + erc20MintAmount, erc20TotalSupplyBefore) + + // Alice should start with amount previously minted + let aliceEVMBalanceBefore = balanceOf(evmAddressHex: aliceCOAAddressHex, erc20AddressHex: erc20AddressHex) + Test.assertEqual(erc20MintAmount, aliceEVMBalanceBefore) + // Cadence total supply should match the amount in escrow + let escrowBalanceBefore = balanceOf(evmAddressHex: getBridgeCOAAddressHex(), erc20AddressHex: erc20AddressHex) + Test.assertEqual(uintCadenceTotalSupplyBefore, escrowBalanceBefore) + + // Alice was the only one minted Cadence tokens, so should have the total supply in Cadence + let aliceCadenceBalanceBefore = getBalance(ownerAddr: alice.address, storagePathIdentifier: "exampleTokenVault") + ?? panic("Problem getting ExampleToken balance") + Test.assertEqual(exampleTokenMintAmount, aliceCadenceBalanceBefore) + Test.assertEqual(cadenceTotalSupplyBefore, aliceCadenceBalanceBefore) + + // Convert the bridge amount to UFix64 for Cadence balance comparison + let ufixBridgeAmount = uint256ToUFix64(erc20MintAmount, decimals: defaultDecimals) + + // Execute bridge from EVM + bridgeTokensFromEVM( + signer: alice, + contractAddr: exampleHandledTokenAccount.address, + contractName: "ExampleHandledToken", + amount: aliceEVMBalanceBefore, + beFailed: false + ) + + // Confirm that Alice's balance is now the total supply, having incremented by the amount bridged into Cadence + let aliceCadenceBalanceAfter = getBalance(ownerAddr: alice.address, storagePathIdentifier: "exampleTokenVault") + ?? panic("Problem getting ExampleToken balance") + let cadenceTotalSupplyAfter = getCadenceTotalSupply( + contractAddress: exampleHandledTokenAccount.address, + contractName: "ExampleHandledToken", + vaultIdentifier: exampleTokenIdentifier + ) ?? panic("Problem getting total supply of Cadence tokens") + Test.assertEqual(cadenceTotalSupplyAfter, aliceCadenceBalanceAfter) + Test.assertEqual(cadenceTotalSupplyAfter, cadenceTotalSupplyBefore + ufixBridgeAmount) + + // Confirm Alice's EVM balance is now 0 + let aliceEVMBalanceAfter = balanceOf(evmAddressHex: aliceCOAAddressHex, erc20AddressHex: erc20AddressHex) + Test.assertEqual(UInt256(0), aliceEVMBalanceAfter) + + // Confirm that the amount in escrow incremented + let escrowBalanceAfter = balanceOf(evmAddressHex: getBridgeCOAAddressHex(), erc20AddressHex: erc20AddressHex) + Test.assertEqual(escrowBalanceBefore + aliceEVMBalanceBefore, escrowBalanceAfter) + + // Ensure the ERC20 balance in circulation remained the same + let erc20TotalSupplyAfter = getEVMTotalSupply(erc20AddressHex: erc20AddressHex) + Test.assertEqual(erc20TotalSupplyBefore, erc20TotalSupplyAfter) +} + +// Now return all liquidity back to EVM +access(all) +fun testBridgeHandledCadenceNativeTokenToEVMSecondSucceeds() { + let erc20AddressHex = getDeployedAddressFromDeployer(name: "erc20") + let aliceCOAAddressHex = getCOAAddressHex(atFlowAddress: alice.address) + + let erc20TotalSupplyBefore = getEVMTotalSupply(erc20AddressHex: erc20AddressHex) + + // Alice should start with amount previously minted + let aliceEVMBalanceBefore = balanceOf(evmAddressHex: aliceCOAAddressHex, erc20AddressHex: erc20AddressHex) + Test.assertEqual(UInt256(0), aliceEVMBalanceBefore) + let aliceCadenceBalanceBefore = getBalance(ownerAddr: alice.address, storagePathIdentifier: "exampleTokenVault") + ?? panic("Problem getting ExampleToken balance") + + // Convert the bridge amount to UInt256 for EVM balance comparison + let uintBridgeAmount = ufix64ToUInt256(aliceCadenceBalanceBefore, decimals: defaultDecimals) + + // Execute bridge to EVM + bridgeTokensToEVM( + signer: alice, + contractAddr: exampleHandledTokenAccount.address, + contractName: "ExampleHandledToken", + amount: aliceCadenceBalanceBefore, + beFailed: false + ) + + let aliceCadenceBalanceAfter = getBalance(ownerAddr: alice.address, storagePathIdentifier: "exampleTokenVault") + ?? panic("Problem getting ExampleToken balance") + Test.assertEqual(0.0, aliceCadenceBalanceAfter) + + // Confirm ownership on EVM side with Alice COA as owner of ERC721 representation + let aliceEVMBalanceAfter = balanceOf(evmAddressHex: aliceCOAAddressHex, erc20AddressHex: erc20AddressHex) + Test.assertEqual(uintBridgeAmount, aliceEVMBalanceAfter) + + // Confirm that the ERC20 balance in circulation remained the same + let erc20TotalSupplyAfter = getEVMTotalSupply(erc20AddressHex: erc20AddressHex) + Test.assertEqual(erc20TotalSupplyBefore, erc20TotalSupplyAfter) + + // Confirm escrow balance is now 0 + let escrowBalance = balanceOf(evmAddressHex: getBridgeCOAAddressHex(), erc20AddressHex: erc20AddressHex) + Test.assertEqual(UInt256(0), escrowBalance) +} diff --git a/cadence/tests/flow_evm_bridge_tests.cdc b/cadence/tests/flow_evm_bridge_tests.cdc index 977812e5..2c45dfea 100644 --- a/cadence/tests/flow_evm_bridge_tests.cdc +++ b/cadence/tests/flow_evm_bridge_tests.cdc @@ -261,7 +261,6 @@ fun testCreateCOASucceeds() { createCOA(signer: alice, fundingAmount: 100.0) let coaAddressHex = getCOAAddressHex(atFlowAddress: alice.address) - Test.assertEqual(40, coaAddressHex.length) } access(all) @@ -273,7 +272,6 @@ fun testBridgeFlowToEVMSucceeds() { // Get EVM $FLOW balance before var aliceCOAAddressHex = getCOAAddressHex(atFlowAddress: alice.address) - Test.assertEqual(40, aliceCOAAddressHex.length) let evmBalanceBefore = getEVMFlowBalance(of: aliceCOAAddressHex) Test.assertEqual(100.0, evmBalanceBefore) @@ -284,7 +282,8 @@ fun testBridgeFlowToEVMSucceeds() { signer: alice, contractAddr: Address(0x03), contractName: "FlowToken", - amount: bridgeAmount + amount: bridgeAmount, + beFailed: false ) // Confirm Alice's token balance is now 0.0 @@ -353,7 +352,6 @@ fun testMintExampleTokenSucceeds() { access(all) fun testMintERC721Succeeds() { let aliceCOAAddressHex = getCOAAddressHex(atFlowAddress: alice.address) - Test.assertEqual(40, aliceCOAAddressHex.length) let erc721AddressHex = getDeployedAddressFromDeployer(name: "erc721") Test.assertEqual(40, erc721AddressHex.length) @@ -371,7 +369,6 @@ fun testMintERC721Succeeds() { access(all) fun testMintERC20Succeeds() { let aliceCOAAddressHex = getCOAAddressHex(atFlowAddress: alice.address) - Test.assertEqual(40, aliceCOAAddressHex.length) let erc20AddressHex = getDeployedAddressFromDeployer(name: "erc20") Test.assertEqual(40, erc20AddressHex.length) @@ -710,7 +707,6 @@ fun testBridgeCadenceNativeNFTToEVMSucceeds() { Test.assertEqual(1, aliceOwnedIDs.length) var aliceCOAAddressHex = getCOAAddressHex(atFlowAddress: alice.address) - Test.assertEqual(40, aliceCOAAddressHex.length) // Execute bridge to EVM bridgeNFTToEVM( @@ -740,7 +736,6 @@ fun testBridgeCadenceNativeNFTToEVMSucceeds() { access(all) fun testBridgeCadenceNativeNFTFromEVMSucceeds() { let aliceCOAAddressHex = getCOAAddressHex(atFlowAddress: alice.address) - Test.assertEqual(40, aliceCOAAddressHex.length) let associatedEVMAddressHex = getAssociatedEVMAddressHex(with: exampleNFTIdentifier) Test.assertEqual(40, associatedEVMAddressHex.length) @@ -776,7 +771,6 @@ fun testBridgeEVMNativeNFTFromEVMSucceeds() { let derivedERC721ContractName = deriveBridgedNFTContractName(evmAddressHex: erc721AddressHex) let bridgedCollectionPathIdentifier = derivedERC721ContractName.concat("Collection") let aliceCOAAddressHex = getCOAAddressHex(atFlowAddress: alice.address) - Test.assertEqual(40, aliceCOAAddressHex.length) bridgeNFTFromEVM( signer: alice, @@ -806,7 +800,6 @@ fun testBridgeEVMNativeNFTToEVMSucceeds() { let derivedERC721ContractName = deriveBridgedNFTContractName(evmAddressHex: erc721AddressHex) let bridgedCollectionPathIdentifier = derivedERC721ContractName.concat("Collection") let aliceCOAAddressHex = getCOAAddressHex(atFlowAddress: alice.address) - Test.assertEqual(40, aliceCOAAddressHex.length) var aliceOwnedIDs = getIDs(ownerAddr: alice.address, storagePathIdentifier: bridgedCollectionPathIdentifier) Test.assertEqual(1, aliceOwnedIDs.length) @@ -835,14 +828,14 @@ fun testBridgeCadenceNativeTokenToEVMSucceeds() { Test.assert(cadenceBalance == exampleTokenMintAmount) var aliceCOAAddressHex = getCOAAddressHex(atFlowAddress: alice.address) - Test.assertEqual(40, aliceCOAAddressHex.length) // Execute bridge to EVM bridgeTokensToEVM( signer: alice, contractAddr: exampleTokenAccount.address, contractName: "ExampleToken", - amount: cadenceBalance + amount: cadenceBalance, + beFailed: false ) let associatedEVMAddressHex = getAssociatedEVMAddressHex(with: exampleTokenIdentifier) @@ -866,7 +859,6 @@ fun testBridgeCadenceNativeTokenFromEVMSucceeds() { Test.assertEqual(40, associatedEVMAddressHex.length) var aliceCOAAddressHex = getCOAAddressHex(atFlowAddress: alice.address) - Test.assertEqual(40, aliceCOAAddressHex.length) // Confirm Alice is starting with 0.0 balance in their Cadence Vault var cadenceBalance = getBalance(ownerAddr: alice.address, storagePathIdentifier: "exampleTokenVault") @@ -885,7 +877,8 @@ fun testBridgeCadenceNativeTokenFromEVMSucceeds() { signer: alice, contractAddr: exampleTokenAccount.address, contractName: "ExampleToken", - amount: evmBalance + amount: evmBalance, + beFailed: false ) // Confirm ExampleToken balance has been bridged back to Alice's Cadence vault @@ -906,7 +899,6 @@ fun testBridgeEVMNativeTokenFromEVMSucceeds() { let derivedERC20ContractName = deriveBridgedTokenContractName(evmAddressHex: erc20AddressHex) let bridgedVaultPathIdentifier = derivedERC20ContractName.concat("Vault") let aliceCOAAddressHex = getCOAAddressHex(atFlowAddress: alice.address) - Test.assertEqual(40, aliceCOAAddressHex.length) // Confirm ownership on EVM side with Alice COA as owner of ERC721 representation var evmBalance = balanceOf(evmAddressHex: aliceCOAAddressHex, erc20AddressHex: erc20AddressHex) @@ -921,7 +913,8 @@ fun testBridgeEVMNativeTokenFromEVMSucceeds() { signer: alice, contractAddr: bridgeAccount.address, contractName: derivedERC20ContractName, - amount: evmBalance + amount: evmBalance, + beFailed: false ) // Confirm EVM balance is no 0 @@ -937,7 +930,6 @@ fun testBridgeEVMNativeTokenFromEVMSucceeds() { // With the bridge executed, confirm the bridge COA escrows the ERC20 tokens let bridgeCOAAddressHex = getCOAAddressHex(atFlowAddress: bridgeAccount.address) - Test.assertEqual(40, bridgeCOAAddressHex.length) let bridgeCOAEscrowBalance = balanceOf(evmAddressHex: bridgeCOAAddressHex, erc20AddressHex: erc20AddressHex) Test.assertEqual(erc20MintAmount, bridgeCOAEscrowBalance) } @@ -950,7 +942,6 @@ fun testBridgeEVMNativeTokenToEVMSucceeds() { let derivedERC20ContractName = deriveBridgedTokenContractName(evmAddressHex: erc20AddressHex) let bridgedVaultPathIdentifier = derivedERC20ContractName.concat("Vault") let aliceCOAAddressHex = getCOAAddressHex(atFlowAddress: alice.address) - Test.assertEqual(40, aliceCOAAddressHex.length) // Confirm Cadence Vault has the expected balance var cadenceBalance = getBalance(ownerAddr: alice.address, storagePathIdentifier: bridgedVaultPathIdentifier) @@ -965,7 +956,6 @@ fun testBridgeEVMNativeTokenToEVMSucceeds() { // Confirm the bridge COA currently escrows the ERC20 tokens we will be bridging let bridgeCOAAddressHex = getCOAAddressHex(atFlowAddress: bridgeAccount.address) - Test.assertEqual(40, bridgeCOAAddressHex.length) var bridgeCOAEscrowBalance = balanceOf(evmAddressHex: bridgeCOAAddressHex, erc20AddressHex: erc20AddressHex) Test.assertEqual(erc20MintAmount, bridgeCOAEscrowBalance) @@ -974,7 +964,8 @@ fun testBridgeEVMNativeTokenToEVMSucceeds() { signer: alice, contractAddr: bridgeAccount.address, contractName: derivedERC20ContractName, - amount: cadenceBalance + amount: cadenceBalance, + beFailed: false ) // Confirm ownership on EVM side with Alice COA as owner of ERC721 representation diff --git a/cadence/tests/test_helpers.cdc b/cadence/tests/test_helpers.cdc index cc9671eb..dda61861 100644 --- a/cadence/tests/test_helpers.cdc +++ b/cadence/tests/test_helpers.cdc @@ -132,7 +132,9 @@ fun getCOAAddressHex(atFlowAddress: Address): String { [atFlowAddress] ) Test.expect(coaAddressResult, Test.beSucceeded()) - return coaAddressResult.returnValue as! String? ?? panic("Problem getting COA address as String") + let coaAddressHex = coaAddressResult.returnValue as! String? ?? panic("Problem getting COA address as String") + Test.assertEqual(40, coaAddressHex.length) + return coaAddressHex } access(all) @@ -164,7 +166,9 @@ fun getDeployedAddressFromDeployer(name: String): String { [name] ) Test.expect(erc721AddressResult, Test.beSucceeded()) - return erc721AddressResult.returnValue as! String? ?? panic("Problem getting COA address as String") + let addressHex = erc721AddressResult.returnValue as! String? ?? panic("Problem getting COA address as String") + Test.assertEqual(40, addressHex.length) + return addressHex } access(all) @@ -247,6 +251,26 @@ fun isOwner(of: UInt256, ownerEVMAddrHex: String, erc721AddressHex: String): Boo return isOwnerResult.returnValue as! Bool? ?? panic("Problem getting owner status") } +access(all) +fun getCadenceTotalSupply(contractAddress: Address, contractName: String, vaultIdentifier: String): UFix64? { + let exampleTokenTotalSupplyResult = _executeScript( + "../scripts/tokens/total_supply.cdc", + [contractAddress, "ExampleHandledToken", vaultIdentifier] + ) + Test.expect(exampleTokenTotalSupplyResult, Test.beSucceeded()) + return exampleTokenTotalSupplyResult.returnValue as! UFix64? +} + +access(all) +fun getEVMTotalSupply(erc20AddressHex: String): UInt256 { + let totalSupplyResult = _executeScript( + "../scripts/utils/total_supply.cdc", + [erc20AddressHex] + ) + Test.expect(totalSupplyResult, Test.beSucceeded()) + return totalSupplyResult.returnValue as! UInt256? ?? panic("Problem getting ERC20 total supply") +} + access(all) fun deriveBridgedNFTContractName(evmAddressHex: String): String { let nameResult = _executeScript( @@ -340,26 +364,26 @@ fun bridgeNFTFromEVM( } access(all) -fun bridgeTokensToEVM(signer: Test.TestAccount, contractAddr: Address, contractName: String, amount: UFix64) { +fun bridgeTokensToEVM(signer: Test.TestAccount, contractAddr: Address, contractName: String, amount: UFix64, beFailed: Bool) { let bridgeResult = _executeTransaction( "../transactions/bridge/tokens/bridge_tokens_to_evm.cdc", [contractAddr, contractName, amount], signer ) - Test.expect(bridgeResult, Test.beSucceeded()) + Test.expect(bridgeResult, beFailed ? Test.beFailed() : Test.beSucceeded()) // TODO: Add event assertions on bridge events. We can't currently import the event types to do this // so state assertions beyond call scope will need to suffice for now } access(all) -fun bridgeTokensFromEVM(signer: Test.TestAccount, contractAddr: Address, contractName: String, amount: UInt256) { +fun bridgeTokensFromEVM(signer: Test.TestAccount, contractAddr: Address, contractName: String, amount: UInt256, beFailed: Bool) { let bridgeResult = _executeTransaction( "../transactions/bridge/tokens/bridge_tokens_from_evm.cdc", [contractAddr, contractName, amount], signer ) - Test.expect(bridgeResult, Test.beSucceeded()) + Test.expect(bridgeResult, beFailed ? Test.beFailed() : Test.beSucceeded()) // TODO: Add event assertions on bridge events. We can't currently import the event types to do this // so state assertions beyond call scope will need to suffice for now diff --git a/cadence/transactions/bridge/admin/enable_token_handler.cdc b/cadence/transactions/bridge/admin/enable_token_handler.cdc new file mode 100644 index 00000000..900dc563 --- /dev/null +++ b/cadence/transactions/bridge/admin/enable_token_handler.cdc @@ -0,0 +1,26 @@ +import "EVM" + +import "EVMUtils" +import "FlowEVMBridgeHandlerInterfaces" +import "FlowEVMBridgeConfig" + +/// Enables the TokenHandler to fulfill bridge requests. +/// +/// @param targetTypeIdentifier: The identifier of the handler's target type. +/// +transaction(targetTypeIdentifier: String) { + + let admin: auth(FlowEVMBridgeHandlerInterfaces.Admin) &FlowEVMBridgeConfig.Admin + + prepare(signer: auth(BorrowValue) &Account) { + self.admin = signer.storage.borrow( + from: FlowEVMBridgeConfig.adminStoragePath + ) ?? panic("Could not borrow FlowEVMBridgeConfig Admin reference") + } + + execute { + let targetType = CompositeType(targetTypeIdentifier) + ?? panic("Invalid Type identifier provided") + self.admin.enableHandler(targetType: targetType) + } +} diff --git a/cadence/transactions/bridge/admin/set_handler_target_evm_address.cdc b/cadence/transactions/bridge/admin/set_handler_target_evm_address.cdc index 0086e9c6..556bc844 100644 --- a/cadence/transactions/bridge/admin/set_handler_target_evm_address.cdc +++ b/cadence/transactions/bridge/admin/set_handler_target_evm_address.cdc @@ -4,11 +4,10 @@ import "EVMUtils" import "FlowEVMBridgeHandlerInterfaces" import "FlowEVMBridgeConfig" -/// Sets the base fee charged for all bridge requests. +/// Sets the target EVM address for the associated type in the configured TokenHandler /// -/// @param newFee: The new base fee to charge for all bridge requests. -/// -/// @emits FlowEVMBridgeConfig.BridgeFeeUpdated(old: FlowEVMBridgeConfig.onboardFee, new: newFee, isOnboarding: false) +/// @param targetTypeIdentifier: The identifier of the target type. +/// @param targetEVMAddressHex: The EVM address of the target EVM contract. /// transaction(targetTypeIdentifier: String, targetEVMAddressHex: String) {