From b3688e3e28a8e1aa1c6bc84cc1cc04ec20092328 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Wed, 24 Apr 2024 18:03:48 -0500 Subject: [PATCH 1/6] consolidate token bridging logic --- cadence/contracts/bridge/FlowEVMBridge.cdc | 123 +++++++----------- .../bridge/FlowEVMBridgeHandlers.cdc | 73 +++-------- .../contracts/bridge/FlowEVMBridgeUtils.cdc | 102 +++++++++++++++ 3 files changed, 169 insertions(+), 129 deletions(-) diff --git a/cadence/contracts/bridge/FlowEVMBridge.cdc b/cadence/contracts/bridge/FlowEVMBridge.cdc index 78a4b99b..9bd72bad 100644 --- a/cadence/contracts/bridge/FlowEVMBridge.cdc +++ b/cadence/contracts/bridge/FlowEVMBridge.cdc @@ -380,6 +380,8 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { !vault.isInstance(Type<@{NonFungibleToken.NFT}>()): "Mixed asset types are not yet supported" self.typeRequiresOnboarding(vault.getType()) == false: "FT must first be onboarded" } + /* Handle $FLOW requests via EVM interface & return */ + // let vaultType = vault.getType() if vaultType == Type<@FlowToken.Vault>() { let flowVault <-vault as! @FlowToken.Vault @@ -387,14 +389,17 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { return } + // Gather the vault balance before acting on the resource let vaultBalance = vault.balance + // Initialize fee amount to 0.0 and assign as appropriate for how the token is handled var feeAmount = 0.0 + + /* TokenHandler coverage */ + // + // Some tokens pre-dating bridge require special case handling - borrow handler and passthrough to fulfill if FlowEVMBridgeConfig.typeHasTokenHandler(vaultType) { - // Some tokens pre-dating bridge require special case handling - borrow handler and passthrough to fulfill let handler = FlowEVMBridgeConfig.borrowTokenHandler(vaultType) ?? panic("Could not retrieve handler for the given type") - assert(handler.isEnabled(), message: "Cannot bridge tokens of this type at this time") - handler.fulfillTokensToEVM(tokens: <-vault, to: to) // Here we assume burning Vault in Cadence which doesn't require storage consumption @@ -403,6 +408,8 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { return } + /* Escrow or burn tokens depending on native environment */ + // // In most all other cases, if Cadence-native then tokens must be escrowed if FlowEVMBridgeUtils.isCadenceNative(type: vaultType) { // Lock the FT balance & calculate the extra used by the FT if any @@ -415,48 +422,30 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { feeAmount = FlowEVMBridgeUtils.calculateBridgeFee(bytes: 0) } + /* Provision fees */ + // // Withdraw fee amount from feeProvider and deposit FlowEVMBridgeUtils.depositFee(feeProvider, feeAmount: feeAmount) + /* Gather identifying information */ + // // Does the bridge control the EVM contract associated with this type? let associatedAddress = FlowEVMBridgeConfig.getEVMAddressAssociated(with: vaultType) ?? panic("No EVMAddress found for vault type") // Convert the vault balance to a UInt256 let decimals = FlowEVMBridgeUtils.getTokenDecimals(evmContractAddress: associatedAddress) let bridgeAmount = FlowEVMBridgeUtils.ufix64ToUInt256(value: vaultBalance, decimals: decimals) - - let toPreBalance = FlowEVMBridgeUtils.balanceOf(owner: to, evmContractAddress: associatedAddress) - + // Determine if the EVM contract is bridge-owned - affects how tokens are transmitted to recipient let isFactoryDeployed = FlowEVMBridgeUtils.isEVMContractBridgeOwned(evmContractAddress: associatedAddress) - // Controlled by the bridge - mint or transfer based on the bridge's EVM contract authority + + /* Transmit tokens to recipient */ + // + // Mint or transfer based on the bridge's EVM contract authority, making needed state assertions to confirm if isFactoryDeployed { - // Mint tokens to the recipient - let callResult: EVM.Result = FlowEVMBridgeUtils.call( - signature: "mint(address,uint256)", - targetEVMAddress: associatedAddress, - args: [to, bridgeAmount], - gasLimit: 15000000, - value: 0.0 - ) - assert(callResult.status == EVM.Status.successful, message: "Tranfer to bridge recipient failed") + FlowEVMBridgeUtils.mustMintERC20(to: to, amount: bridgeAmount, erc20Address: associatedAddress) } else { - // Not bridge-controlled, transfer existing ownership - let callResult: EVM.Result = FlowEVMBridgeUtils.call( - signature: "transfer(address,uint256)", - targetEVMAddress: associatedAddress, - args: [to, bridgeAmount], - gasLimit: 15000000, - value: 0.0 - ) - assert(callResult.status == EVM.Status.successful, message: "Tranfer to bridge recipient failed") + FlowEVMBridgeUtils.mustTransferERC20(to: to, amount: bridgeAmount, erc20Address: associatedAddress) } - - // Ensure bridge to recipient was succcessful - let toPostBalance = FlowEVMBridgeUtils.balanceOf(owner: to, evmContractAddress: associatedAddress) - assert( - toPostBalance == toPreBalance + bridgeAmount, - message: "Transfer to bridge recipient failed" - ) } /// Public entrypoint to bridge FTs from EVM to Cadence @@ -487,16 +476,19 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { !type.isInstance(Type<@FlowToken.Vault>()): "Must use the CadenceOwnedAccount interface to bridge $FLOW from EVM" self.typeRequiresOnboarding(type) == false: "NFT must first be onboarded" } + /* Provision fees */ + // // Withdraw from feeProvider and deposit to self let feeAmount = FlowEVMBridgeUtils.calculateBridgeFee(bytes: 0) FlowEVMBridgeUtils.depositFee(feeProvider, feeAmount: feeAmount) + /* TokenHandler case coverage */ + // + // Some tokens pre-dating bridge require special case handling. If such a case, fulfill via the related handler if FlowEVMBridgeConfig.typeHasTokenHandler(type) { - // Some tokens pre-dating bridge require special case handling - borrow handler and passthrough to fulfill + // - borrow handler and passthrough to fulfill let handler = FlowEVMBridgeConfig.borrowTokenHandler(type) ?? panic("Could not retrieve handler for the given type") - assert(handler.isEnabled(), message: "Cannot bridge tokens of this type at this time") - return <-handler.fulfillTokensFromEVM( owner: owner, type: type, @@ -505,56 +497,37 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { ) } + /* Gather identifying information */ + // // Get the EVMAddress of the ERC20 contract associated with the type let associatedAddress = FlowEVMBridgeConfig.getEVMAddressAssociated(with: type) ?? panic("No EVMAddress found for token type") - - // Ensure the caller is has sufficient balance to bridge the requested amount - let hasSufficientBalance = FlowEVMBridgeUtils.hasSufficientBalance( - amount: amount, - owner: owner, - evmContractAddress: associatedAddress - ) - assert(hasSufficientBalance, message: "Caller does not have sufficient balance to bridge requested tokens") - - // Get the owner and escrow balance of the token before executing the protected transfer call - let bridgeCOAAddress = self.getBridgeCOAEVMAddress() - let ownerBalanceBefore = FlowEVMBridgeUtils.balanceOf(owner: owner, evmContractAddress: associatedAddress) - let bridgeBalanceBefore = FlowEVMBridgeUtils.balanceOf( - owner: bridgeCOAAddress, - evmContractAddress: associatedAddress - ) - - // Execute the transfer from the calling owner to the bridge's COA, escrowing the tokens in EVM - let callResult = protectedTransferCall() - assert(callResult.status == EVM.Status.successful, message: "Transfer to bridge COA failed") - - // Confirm the transfer of the expected was successful in both sending owner and recipient escrow - let ownerBalanceAfter = FlowEVMBridgeUtils.balanceOf(owner: owner, evmContractAddress: associatedAddress) - let bridgeBalanceAfter = FlowEVMBridgeUtils.balanceOf( - owner: bridgeCOAAddress, - evmContractAddress: associatedAddress - ) - assert( - ownerBalanceAfter == ownerBalanceBefore - amount, - message: "Transfer to bridge COA failed - cannot bridge FT without bridge escrow" - ) - assert( - bridgeBalanceAfter == bridgeBalanceBefore + amount, - message: "Transfer to bridge COA failed - cannot bridge FT without bridge escrow" - ) - + // Find the Cadence defining address and contract name let definingAddress = FlowEVMBridgeUtils.getContractAddress(fromType: type)! let definingContractName = FlowEVMBridgeUtils.getContractName(fromType: type)! + // Convert the amount to a ufix64 so the amount can be settled on the Cadence side + let ufixAmount = FlowEVMBridgeUtils.convertERC20AmountToCadenceAmount(amount, erc20Address: associatedAddress) - let decimals = FlowEVMBridgeUtils.getTokenDecimals(evmContractAddress: associatedAddress) - let ufixAmount = FlowEVMBridgeUtils.uint256ToUFix64(value: amount, decimals: decimals) + /* Execute the transfer call and make needed state assertions */ + // + FlowEVMBridgeUtils.mustExecuteProtectedERC20TransferCall( + owner: owner, + amount: amount, + erc20Address: associatedAddress, + protectedTransferCall: protectedTransferCall + ) + + /* Bridge-defined tokens are minted in Cadence */ + // // If the Cadence Vault is bridge-defined, mint the tokens if definingAddress == self.account.address { let minter = getAccount(definingAddress).contracts.borrow<&{IEVMBridgeTokenMinter}>(name: definingContractName)! return <- minter.mintTokens(amount: ufixAmount) } - // Otherwise, the bridge will need to unlock them from escrow + + /* Cadence-native tokens are withdrawn from escrow */ + // + // Confirm the EVM defining contract is bridge-owned before burning tokens assert( FlowEVMBridgeUtils.isEVMContractBridgeOwned(evmContractAddress: associatedAddress), message: "Unexpected error bridging FT from EVM" @@ -568,6 +541,8 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { value: 0.0 ) assert(burnResult.status == EVM.Status.successful, message: "Burn of EVM tokens failed") + + // Unlock from escrow and return return <-FlowEVMBridgeTokenEscrow.unlockTokens(type: type, amount: ufixAmount) } diff --git a/cadence/contracts/bridge/FlowEVMBridgeHandlers.cdc b/cadence/contracts/bridge/FlowEVMBridgeHandlers.cdc index 80220abf..bce5c9d4 100644 --- a/cadence/contracts/bridge/FlowEVMBridgeHandlers.cdc +++ b/cadence/contracts/bridge/FlowEVMBridgeHandlers.cdc @@ -33,7 +33,7 @@ access(all) contract FlowEVMBridgeHandlers { /// In order for this to occur, neither the Cadence token nor the EVM contract can be onboarded to the bridge - in /// essence, neither side of the asset can be onboarded to the bridge. /// The Handler must be configured in the bridge via the HandlerConfigurator. Once added, the bridge will filter - /// requests to bridge the token Vault to EVM through this Handler which cannot be enabled until a target EVM + /// requests to bridge the token Vault to EVM through this Handler which cannot be enabled until a target EVM /// address is set. Once the corresponding EVM contract address is known, it can be set and the Handler. It's also /// suggested that the Handler only be enabled once sufficient liquidity has been arranged in bridge escrow on the /// EVM side. @@ -99,42 +99,18 @@ access(all) contract FlowEVMBridgeHandlers { tokens: @{FungibleToken.Vault}, to: EVM.EVMAddress ) { + let evmAddress = self.getTargetEVMAddress()! + // Get values from vault and burn let amount = tokens.balance let uintAmount = FlowEVMBridgeUtils.ufix64ToUInt256( value: amount, - decimals: FlowEVMBridgeUtils.getTokenDecimals(evmContractAddress: self.getTargetEVMAddress()!) + decimals: FlowEVMBridgeUtils.getTokenDecimals(evmContractAddress: evmAddress) ) Burner.burn(<-tokens) - // Get the recipient and escrow balances before transferring - let toPreBalance = FlowEVMBridgeUtils.balanceOf(owner: to, evmContractAddress: self.targetEVMAddress!) - let bridgePreBalance = FlowEVMBridgeUtils.balanceOf( - owner: FlowEVMBridgeUtils.getBridgeCOAEVMAddress(), - evmContractAddress: self.targetEVMAddress! - ) - - // Call the EVM contract to transfer escrowed tokens - let callResult: EVM.Result = FlowEVMBridgeUtils.call( - signature: "transfer(address,uint256)", - targetEVMAddress: self.getTargetEVMAddress()!, - args: [to, uintAmount], - gasLimit: 15000000, - value: 0.0 - ) - assert(callResult.status == EVM.Status.successful, message: "Tranfer to bridge recipient failed") - - // Get the resulting balances after transfer - let toPostBalance = FlowEVMBridgeUtils.balanceOf(owner: to, evmContractAddress: self.targetEVMAddress!) - let bridgePostBalance = FlowEVMBridgeUtils.balanceOf( - owner: FlowEVMBridgeUtils.getBridgeCOAEVMAddress(), - evmContractAddress: self.targetEVMAddress! - ) - - // Recipient should have received the tokens and bridge escrow should have decreased - assert(toPostBalance == toPreBalance + uintAmount, message: "Transfer to bridge recipient failed") - assert(bridgePostBalance == bridgePreBalance - uintAmount, message: "Transfer to bridge escrow failed") + FlowEVMBridgeUtils.mustTransferERC20(to: to, amount: uintAmount, erc20Address: evmAddress) } /// Fulfill a request to bridge tokens from EVM to Cadence, minting the provided amount of tokens in Cadence @@ -154,33 +130,20 @@ access(all) contract FlowEVMBridgeHandlers { amount: UInt256, protectedTransferCall: fun (): EVM.Result ): @{FungibleToken.Vault} { - // Convert the amount to a UFix64 - let ufixAmount = FlowEVMBridgeUtils.uint256ToUFix64( - value: amount, - decimals: FlowEVMBridgeUtils.getTokenDecimals(evmContractAddress: self.getTargetEVMAddress()!) - ) + let evmAddress = self.getTargetEVMAddress()! - // Get the owner and escrow balances before transfer - let ownerPreBalance = FlowEVMBridgeUtils.balanceOf(owner: owner, evmContractAddress: self.targetEVMAddress!) - let bridgePreBalance = FlowEVMBridgeUtils.balanceOf( - owner: FlowEVMBridgeUtils.getBridgeCOAEVMAddress(), - evmContractAddress: self.targetEVMAddress! + // Convert the amount to a UFix64 + let ufixAmount = FlowEVMBridgeUtils.convertERC20AmountToCadenceAmount( + amount, + erc20Address: evmAddress ) - // Call the protected transfer function which should execute a transfer call from the owner to escrow - let transferResult = protectedTransferCall() - assert(transferResult.status == EVM.Status.successful, message: "Transfer via callback failed") - - // Get the resulting balances after transfer - let ownerPostBalance = FlowEVMBridgeUtils.balanceOf(owner: owner, evmContractAddress: self.targetEVMAddress!) - let bridgePostBalance = FlowEVMBridgeUtils.balanceOf( - owner: FlowEVMBridgeUtils.getBridgeCOAEVMAddress(), - evmContractAddress: self.targetEVMAddress! - ) - - // Confirm the transfer of the expected was successful in both sending owner and recipient escrow - assert(ownerPostBalance == ownerPreBalance - amount, message: "Transfer to owner failed") - assert(bridgePostBalance == bridgePreBalance + amount, message: "Transfer to bridge escrow failed") + FlowEVMBridgeUtils.mustExecuteProtectedERC20TransferCall( + owner: owner, + amount: amount, + erc20Address: evmAddress, + protectedTransferCall: protectedTransferCall + ) // After state confirmation, mint the tokens and return let minter = self.borrowMinter() ?? panic("Minter not set") @@ -196,7 +159,7 @@ access(all) contract FlowEVMBridgeHandlers { self.targetType = type } - /// Sets the target EVM address for the handler + /// Sets the target EVM address for the handler access(FlowEVMBridgeHandlerInterfaces.Admin) fun setTargetEVMAddress(_ address: EVM.EVMAddress) { self.targetEVMAddress = address @@ -211,7 +174,7 @@ access(all) contract FlowEVMBridgeHandlers { self.minter <-! minter } - /// Enables the handler for request handling. The + /// Enables the handler for request handling. The access(FlowEVMBridgeHandlerInterfaces.Admin) fun enableBridging() { pre { diff --git a/cadence/contracts/bridge/FlowEVMBridgeUtils.cdc b/cadence/contracts/bridge/FlowEVMBridgeUtils.cdc index 99e0386e..92d775df 100644 --- a/cadence/contracts/bridge/FlowEVMBridgeUtils.cdc +++ b/cadence/contracts/bridge/FlowEVMBridgeUtils.cdc @@ -466,6 +466,14 @@ contract FlowEVMBridgeUtils { return decodedResult[0] as! UInt256 } + access(all) + fun convertERC20AmountToCadenceAmount(_ amount: UInt256, erc20Address: EVM.EVMAddress): UFix64 { + return self.uint256ToUFix64( + value: amount, + decimals: self.getTokenDecimals(evmContractAddress: erc20Address) + ) + } + /************************ Derivation Utils ************************/ @@ -727,6 +735,100 @@ contract FlowEVMBridgeUtils { ) } + access(account) + fun mustMintERC20(to: EVM.EVMAddress, amount: UInt256, erc20Address: EVM.EVMAddress) { + let toPreBalance = FlowEVMBridgeUtils.balanceOf(owner: to, evmContractAddress: erc20Address) + // Mint tokens to the recipient + let mintResult: EVM.Result = FlowEVMBridgeUtils.call( + signature: "mint(address,uint256)", + targetEVMAddress: erc20Address, + args: [to, amount], + gasLimit: 15000000, + value: 0.0 + ) + assert(mintResult.status == EVM.Status.successful, message: "Tranfer to bridge recipient failed") + // Ensure bridge to recipient was succcessful + let toPostBalance = FlowEVMBridgeUtils.balanceOf(owner: to, evmContractAddress: erc20Address) + assert( + toPostBalance == toPreBalance + amount, + message: "Transfer to bridge recipient failed" + ) + } + + access(account) + fun mustTransferERC20(to: EVM.EVMAddress, amount: UInt256, erc20Address: EVM.EVMAddress) { + let bridgeCOAAddress = self.getBridgeCOAEVMAddress() + + let toPreBalance = FlowEVMBridgeUtils.balanceOf(owner: to, evmContractAddress: erc20Address) + let escrowPreBalance = FlowEVMBridgeUtils.balanceOf( + owner: bridgeCOAAddress, + evmContractAddress: erc20Address + ) + + // Mint tokens to the recipient + let mintResult: EVM.Result = FlowEVMBridgeUtils.call( + signature: "transfer(address,uint256)", + targetEVMAddress: erc20Address, + args: [to, amount], + gasLimit: 15000000, + value: 0.0 + ) + assert(mintResult.status == EVM.Status.successful, message: "Tranfer to bridge recipient failed") + + // Ensure bridge to recipient was succcessful + let toPostBalance = FlowEVMBridgeUtils.balanceOf(owner: to, evmContractAddress: erc20Address) + let escrowPostBalance = FlowEVMBridgeUtils.balanceOf( + owner: bridgeCOAAddress, + evmContractAddress: erc20Address + ) + assert( + toPostBalance == toPreBalance + amount, + message: "Transfer to bridge COA failed - cannot bridge FT without bridge escrow" + ) + assert( + escrowPostBalance == escrowPreBalance - amount, + message: "Transfer to bridge COA failed - cannot bridge FT without bridge escrow" + ) + } + + access(account) + fun mustExecuteProtectedERC20TransferCall( + owner: EVM.EVMAddress, + amount: UInt256, + erc20Address: EVM.EVMAddress, + protectedTransferCall: fun (): EVM.Result + ) { + // Ensure the caller is has sufficient balance to bridge the requested amount + let hasSufficientBalance = FlowEVMBridgeUtils.hasSufficientBalance( + amount: amount, + owner: owner, + evmContractAddress: erc20Address + ) + assert(hasSufficientBalance, message: "Caller does not have sufficient balance to bridge requested tokens") + + // Get the owner and escrow balances before transfer + let ownerPreBalance = FlowEVMBridgeUtils.balanceOf(owner: owner, evmContractAddress: erc20Address) + let bridgePreBalance = FlowEVMBridgeUtils.balanceOf( + owner: FlowEVMBridgeUtils.getBridgeCOAEVMAddress(), + evmContractAddress: erc20Address + ) + + // Call the protected transfer function which should execute a transfer call from the owner to escrow + let transferResult = protectedTransferCall() + assert(transferResult.status == EVM.Status.successful, message: "Transfer via callback failed") + + // Get the resulting balances after transfer + let ownerPostBalance = FlowEVMBridgeUtils.balanceOf(owner: owner, evmContractAddress: erc20Address) + let bridgePostBalance = FlowEVMBridgeUtils.balanceOf( + owner: FlowEVMBridgeUtils.getBridgeCOAEVMAddress(), + evmContractAddress: erc20Address + ) + + // Confirm the transfer of the expected was successful in both sending owner and recipient escrow + assert(ownerPostBalance == ownerPreBalance - amount, message: "Transfer to owner failed") + assert(bridgePostBalance == bridgePreBalance + amount, message: "Transfer to bridge escrow failed") + } + init(bridgeFactoryBytecodeHex: String) { self.delimiter = "_" self.contractNamePrefixes = { From e008d1e64a1ea7845578d069800fe0d3702fcd08 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Wed, 24 Apr 2024 18:22:56 -0500 Subject: [PATCH 2/6] update contract comments --- cadence/contracts/bridge/FlowEVMBridge.cdc | 55 +++++++++++-------- .../bridge/FlowEVMBridgeHandlers.cdc | 5 +- .../contracts/bridge/FlowEVMBridgeUtils.cdc | 30 ++++++++++ 3 files changed, 62 insertions(+), 28 deletions(-) diff --git a/cadence/contracts/bridge/FlowEVMBridge.cdc b/cadence/contracts/bridge/FlowEVMBridge.cdc index 9bd72bad..8c5043ef 100644 --- a/cadence/contracts/bridge/FlowEVMBridge.cdc +++ b/cadence/contracts/bridge/FlowEVMBridge.cdc @@ -97,21 +97,24 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { pre { type != Type<@FlowToken.Vault>(): "$FLOW cannot be bridged via the VM bridge - use the CadenceOwnedAccount interface" - feeProvider.isAvailableToWithdraw(amount: FlowEVMBridgeConfig.onboardFee): - "Insufficient fee available via feeProvider" self.typeRequiresOnboarding(type) == true: "Onboarding is not needed for this type" + FlowEVMBridgeUtils.typeAllowsBridging(type): + "This type is not supported as defined by the project's development team" FlowEVMBridgeUtils.isCadenceNative(type: type): "Only Cadence-native assets can be onboarded by Type" } - // Ensure the project has not opted out of bridge support - assert( - FlowEVMBridgeUtils.typeAllowsBridging(type), - message: "This type is not supported as defined by the project's development team" - ) + /* Provision fees */ + // // Withdraw from feeProvider and deposit to self FlowEVMBridgeUtils.depositFee(feeProvider, feeAmount: FlowEVMBridgeConfig.onboardFee) + + /* EVM setup */ + // // Deploy an EVM defining contract via the FlowBridgeFactory.sol contract let onboardingValues = self.deployEVMContract(forAssetType: type) - // Initialize bridge escrow for the asset + + /* Cadence escrow setup */ + // + // Initialize bridge escrow for the asset based on its type if type.isSubtype(of: Type<@{NonFungibleToken.NFT}>()) { FlowEVMBridgeNFTEscrow.initializeEscrow( forType: type, @@ -133,6 +136,8 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { panic("Attempted to onboard unsupported type: ".concat(type.identifier)) } + /* Confirmation */ + // assert( FlowEVMBridgeNFTEscrow.isInitialized(forType: type) || FlowEVMBridgeTokenEscrow.isInitialized(forType: type), message: "Failed to initialize escrow for given type" @@ -157,10 +162,8 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { _ address: EVM.EVMAddress, feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider} ) { - pre { - feeProvider.isAvailableToWithdraw(amount: FlowEVMBridgeConfig.onboardFee): - "Insufficient fee available via feeProvider" - } + /* Validate the EVM contract */ + // // Ensure the project has not opted out of bridge support assert( FlowEVMBridgeUtils.evmAddressAllowsBridging(address), @@ -170,17 +173,23 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { self.evmAddressRequiresOnboarding(address) == true, message: "Onboarding is not needed for this contract" ) + + /* Provision fees */ + // // Withdraw fee from feeProvider and deposit FlowEVMBridgeUtils.depositFee(feeProvider, feeAmount: FlowEVMBridgeConfig.onboardFee) + + /* Setup Cadence-defining contract */ + // // Deploy a defining Cadence contract to the bridge account self.deployDefiningContract(evmContractAddress: address) } /************************* - Public NFT Handling + NFT Handling **************************/ - /// Public entrypoint to bridge NFTs from Cadence to EVM. + /// Public entrypoint to bridge NFTs from Cadence to EVM as ERC721. /// /// @param token: The NFT to be bridged /// @param to: The NFT recipient in FlowEVM @@ -281,7 +290,7 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { } } - /// Public entrypoint to bridge NFTs from EVM to Cadence + /// Entrypoint to bridge ERC721 from EVM to Cadence as NonFungibleToken.NFT /// /// @param owner: The EVM address of the NFT owner. Current ownership and successful transfer (via /// `protectedTransferCall`) is validated before the bridge request is executed. @@ -303,8 +312,6 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { protectedTransferCall: fun (): EVM.Result ): @{NonFungibleToken.NFT} { pre { - feeProvider.isAvailableToWithdraw(amount: FlowEVMBridgeUtils.calculateBridgeFee(bytes: 0)): - "Insufficient fee paid" !type.isSubtype(of: Type<@{FungibleToken.Vault}>()): "Mixed asset types are not yet supported" self.typeRequiresOnboarding(type) == false: "NFT must first be onboarded" } @@ -361,10 +368,10 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { } /************************** - Public FT Handling + FT Handling ***************************/ - /// Public entrypoint to bridge FTs from Cadence to EVM. + /// Public entrypoint to bridge FTs from Cadence to EVM as ERC20 tokens. /// /// @param vault: The fungible token Vault to be bridged /// @param to: The fungible token recipient in EVM @@ -433,8 +440,10 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { let associatedAddress = FlowEVMBridgeConfig.getEVMAddressAssociated(with: vaultType) ?? panic("No EVMAddress found for vault type") // Convert the vault balance to a UInt256 - let decimals = FlowEVMBridgeUtils.getTokenDecimals(evmContractAddress: associatedAddress) - let bridgeAmount = FlowEVMBridgeUtils.ufix64ToUInt256(value: vaultBalance, decimals: decimals) + let bridgeAmount = FlowEVMBridgeUtils.convertCadenceAmountToERC20Amount( + vaultBalance, + erc20Address: associatedAddress + ) // Determine if the EVM contract is bridge-owned - affects how tokens are transmitted to recipient let isFactoryDeployed = FlowEVMBridgeUtils.isEVMContractBridgeOwned(evmContractAddress: associatedAddress) @@ -448,7 +457,7 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { } } - /// Public entrypoint to bridge FTs from EVM to Cadence + /// Entrypoint to bridge ERC20 tokens from EVM to Cadence as FungibleToken Vaults /// /// @param owner: The EVM address of the FT owner. Current ownership and successful transfer (via /// `protectedTransferCall`) is validated before the bridge request is executed. @@ -470,8 +479,6 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { protectedTransferCall: fun (): EVM.Result ): @{FungibleToken.Vault} { pre { - feeProvider.isAvailableToWithdraw(amount: FlowEVMBridgeUtils.calculateBridgeFee(bytes: 0)): - "Insufficient fee paid" !type.isSubtype(of: Type<@{NonFungibleToken.Collection}>()): "Mixed asset types are not yet supported" !type.isInstance(Type<@FlowToken.Vault>()): "Must use the CadenceOwnedAccount interface to bridge $FLOW from EVM" self.typeRequiresOnboarding(type) == false: "NFT must first be onboarded" diff --git a/cadence/contracts/bridge/FlowEVMBridgeHandlers.cdc b/cadence/contracts/bridge/FlowEVMBridgeHandlers.cdc index bce5c9d4..17306521 100644 --- a/cadence/contracts/bridge/FlowEVMBridgeHandlers.cdc +++ b/cadence/contracts/bridge/FlowEVMBridgeHandlers.cdc @@ -103,10 +103,7 @@ access(all) contract FlowEVMBridgeHandlers { // Get values from vault and burn let amount = tokens.balance - let uintAmount = FlowEVMBridgeUtils.ufix64ToUInt256( - value: amount, - decimals: FlowEVMBridgeUtils.getTokenDecimals(evmContractAddress: evmAddress) - ) + let uintAmount = FlowEVMBridgeUtils.convertCadenceAmountToERC20Amount(amount, erc20Address: evmAddress) Burner.burn(<-tokens) diff --git a/cadence/contracts/bridge/FlowEVMBridgeUtils.cdc b/cadence/contracts/bridge/FlowEVMBridgeUtils.cdc index 92d775df..c2695a5e 100644 --- a/cadence/contracts/bridge/FlowEVMBridgeUtils.cdc +++ b/cadence/contracts/bridge/FlowEVMBridgeUtils.cdc @@ -466,6 +466,14 @@ contract FlowEVMBridgeUtils { return decodedResult[0] as! UInt256 } + /// Converts the given amount of ERC20 tokens to the equivalent amount in FLOW tokens based on the ERC20s decimals + /// value. Reverts on EVM call failure. + /// + /// @param amount: The amount of ERC20 tokens to convert + /// @param erc20Address: The EVM contract address of the ERC20 token + /// + /// @return the equivalent amount in FLOW tokens as a UFix64 + /// access(all) fun convertERC20AmountToCadenceAmount(_ amount: UInt256, erc20Address: EVM.EVMAddress): UFix64 { return self.uint256ToUFix64( @@ -474,6 +482,19 @@ contract FlowEVMBridgeUtils { ) } + /// Converts the given amount of Cadence fungible tokens to the equivalent amount in ERC20 tokens based on the + /// ERC20s decimals. Reverts on EVM call failure. + /// + /// @param amount: The amount of Cadence fungible tokens to convert + /// @param erc20Address: The EVM contract address of the ERC20 token + /// + /// @return the equivalent amount in ERC20 tokens as a UInt256 + /// + access(all) + fun convertCadenceAmountToERC20Amount(_ amount: UFix64, erc20Address: EVM.EVMAddress): UInt256 { + return self.ufix64ToUInt256(value: amount, decimals: self.getTokenDecimals(evmContractAddress: erc20Address)) + } + /************************ Derivation Utils ************************/ @@ -735,6 +756,8 @@ contract FlowEVMBridgeUtils { ) } + /// Mints ERC20 tokens to the recipient and confirms that the recipient's balance was updated + /// access(account) fun mustMintERC20(to: EVM.EVMAddress, amount: UInt256, erc20Address: EVM.EVMAddress) { let toPreBalance = FlowEVMBridgeUtils.balanceOf(owner: to, evmContractAddress: erc20Address) @@ -755,6 +778,9 @@ contract FlowEVMBridgeUtils { ) } + /// Transfers ERC20 tokens to the recipient and confirms that the recipient's balance was incremented and the escrow + /// balance was decremented by the requested amount. + /// access(account) fun mustTransferERC20(to: EVM.EVMAddress, amount: UInt256, erc20Address: EVM.EVMAddress) { let bridgeCOAAddress = self.getBridgeCOAEVMAddress() @@ -791,6 +817,10 @@ contract FlowEVMBridgeUtils { ) } + /// Executes the provided method, assumed to be a protected transfer call, and confirms that the transfer was + /// successful by validating that the named owner's balance was decremented by the requested amount and the bridge + /// escrow balance was incremented by the same amount. + /// access(account) fun mustExecuteProtectedERC20TransferCall( owner: EVM.EVMAddress, From dc137e1a3c6759c198518c65ecca656e6be2b643 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Wed, 24 Apr 2024 19:04:12 -0500 Subject: [PATCH 3/6] add helpers to FlowEVMBridgeUtils to simplify NFT bridging logic --- cadence/contracts/bridge/FlowEVMBridge.cdc | 121 +++++++----------- .../contracts/bridge/FlowEVMBridgeUtils.cdc | 114 +++++++++++++++++ 2 files changed, 161 insertions(+), 74 deletions(-) diff --git a/cadence/contracts/bridge/FlowEVMBridge.cdc b/cadence/contracts/bridge/FlowEVMBridge.cdc index 8c5043ef..cfedf58c 100644 --- a/cadence/contracts/bridge/FlowEVMBridge.cdc +++ b/cadence/contracts/bridge/FlowEVMBridge.cdc @@ -205,10 +205,14 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { !token.isInstance(Type<@{FungibleToken.Vault}>()): "Mixed asset types are not yet supported" self.typeRequiresOnboarding(token.getType()) == false: "NFT must first be onboarded" } + /* Gather identifying information */ + // let tokenType = token.getType() let tokenID = token.id let evmID = CrossVMNFT.getEVMID(from: &token as &{NonFungibleToken.NFT}) ?? UInt256(token.id) + /* Metadata assignement */ + // // Grab the URI from the NFT if available var uri: String = "" // Default to project-specified URI @@ -219,6 +223,8 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { uri = SerializeMetadata.serializeNFTMetadataAsURI(&token as &{NonFungibleToken.NFT}) } + /* Secure NFT in escrow & deposit calculated fees */ + // // Lock the NFT & calculate the storage used by the NFT let storageUsed = FlowEVMBridgeNFTEscrow.lockNFT(<-token) // Calculate the bridge fee on current rates @@ -226,67 +232,34 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { // Withdraw fee from feeProvider and deposit FlowEVMBridgeUtils.depositFee(feeProvider, feeAmount: feeAmount) + /* Determine EVM handling */ + // // Does the bridge control the EVM contract associated with this type? let associatedAddress = FlowEVMBridgeConfig.getEVMAddressAssociated(with: tokenType) ?? panic("No EVMAddress found for token type") let isFactoryDeployed = FlowEVMBridgeUtils.isEVMContractBridgeOwned(evmContractAddress: associatedAddress) - // Controlled by the bridge - mint or transfer based on existence - if isFactoryDeployed { + + /* Third-party controlled ERC721 handling */ + // + // Not bridge-controlled, transfer existing ownership + if !isFactoryDeployed { + FlowEVMBridgeUtils.mustSafeTransferERC721(erc721Address: associatedAddress, to: to, id: evmID) + return + } - // Check if the ERC721 exists - let existsResponse = EVM.decodeABI( - types: [Type()], - data: FlowEVMBridgeUtils.call( - signature: "exists(uint256)", - targetEVMAddress: associatedAddress, - args: [evmID], - gasLimit: 12000000, - value: 0.0 - ).data, - ) - assert(existsResponse.length == 1, message: "Invalid response length") - let exists = existsResponse[0] as! Bool - if exists { - // If so transfer - let transferResult: EVM.Result = FlowEVMBridgeUtils.call( - signature: "safeTransferFrom(address,address,uint256)", - targetEVMAddress: associatedAddress, - args: [self.getBridgeCOAEVMAddress(), to, evmID], - gasLimit: 15000000, - value: 0.0 - ) - assert(transferResult.status == EVM.Status.successful, message: "Tranfer to bridge recipient failed") - - // And update the URI to reflect current metadata - let updateURIResult: EVM.Result = FlowEVMBridgeUtils.call( - signature: "updateTokenURI(uint256,string)", - targetEVMAddress: associatedAddress, - args: [evmID, uri], - gasLimit: 15000000, - value: 0.0 - ) - assert(updateURIResult.status == EVM.Status.successful, message: "Tranfer to bridge recipient failed") - } else { - // Otherwise mint with current URI - let callResult: EVM.Result = FlowEVMBridgeUtils.call( - signature: "safeMint(address,uint256,string)", - targetEVMAddress: associatedAddress, - args: [to, evmID, uri], - gasLimit: 15000000, - value: 0.0 - ) - assert(callResult.status == EVM.Status.successful, message: "Tranfer to bridge recipient failed") - } + /* Bridge-owned ERC721 handling */ + // + // Check if the ERC721 exists in the EVM contract - determines if bridge mints or transfers + let exists = FlowEVMBridgeUtils.erc721Exists(erc721Address: associatedAddress, id: evmID) + if exists { + // Transfer the existing NFT + FlowEVMBridgeUtils.mustSafeTransferERC721(erc721Address: associatedAddress, to: to, id: evmID) + + // And update the URI to reflect current metadata + FlowEVMBridgeUtils.mustUpdateTokenURI(erc721Address: associatedAddress, id: evmID, uri: uri) } else { - // Not bridge-controlled, transfer existing ownership - let callResult: EVM.Result = FlowEVMBridgeUtils.call( - signature: "safeTransferFrom(address,address,uint256)", - targetEVMAddress: associatedAddress, - args: [self.getBridgeCOAEVMAddress(), to, evmID], - gasLimit: 15000000, - value: 0.0 - ) - assert(callResult.status == EVM.Status.successful, message: "Transfer to bridge recipient failed") + // Otherwise mint with current URI + FlowEVMBridgeUtils.mustSafeMintERC721(erc721Address: associatedAddress, to: to, id: evmID, uri: uri) } } @@ -315,40 +288,36 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { !type.isSubtype(of: Type<@{FungibleToken.Vault}>()): "Mixed asset types are not yet supported" self.typeRequiresOnboarding(type) == false: "NFT must first be onboarded" } + /* Provision fee */ + // // Withdraw from feeProvider and deposit to self let feeAmount = FlowEVMBridgeUtils.calculateBridgeFee(bytes: 0) FlowEVMBridgeUtils.depositFee(feeProvider, feeAmount: feeAmount) + /* Execute escrow transfer */ + // // Get the EVMAddress of the ERC721 contract associated with the type let associatedAddress = FlowEVMBridgeConfig.getEVMAddressAssociated(with: type) ?? panic("No EVMAddress found for token type") - - // Ensure the caller is either the current owner or approved for the NFT - let isAuthorized: Bool = FlowEVMBridgeUtils.isOwnerOrApproved( - ofNFT: id, + // Execute the transfer call and make needed state assertions to confirm escrow from named owner + FlowEVMBridgeUtils.mustExecuteERC721ProtectedTransferCall( owner: owner, - evmContractAddress: associatedAddress - ) - assert(isAuthorized, message: "Caller is not the owner of or approved for requested NFT") - - // Execute the transfer from the calling owner to the bridge's COA, escrowing the NFT in EVM - let callResult = protectedTransferCall() - assert(callResult.status == EVM.Status.successful, message: "Transfer to bridge COA failed") - - // Ensure the bridge is now the owner of the NFT after the preceding transfer - let isEscrowed: Bool = FlowEVMBridgeUtils.isOwner( - ofNFT: id, - owner: self.getBridgeCOAEVMAddress(), - evmContractAddress: associatedAddress + id: id, + erc721Address: associatedAddress, + protectedTransferCall: protectedTransferCall ) - assert(isEscrowed, message: "Transfer to bridge COA failed - cannot bridge NFT without bridge escrow") + /* Gather identifying info */ + // // Derive the defining Cadence contract name & address & attempt to borrow it as IEVMBridgeNFTMinter let contractName = FlowEVMBridgeUtils.getContractName(fromType: type)! let contractAddress = FlowEVMBridgeUtils.getContractAddress(fromType: type)! let nftContract = getAccount(contractAddress).contracts.borrow<&{IEVMBridgeNFTMinter}>(name: contractName) // Get the token URI from the ERC721 contract let uri = FlowEVMBridgeUtils.getTokenURI(evmContractAddress: associatedAddress, id: id) + + /* Unlock escrowed NFTs */ + // // If the NFT is currently locked, unlock and return if let cadenceID = FlowEVMBridgeNFTEscrow.getLockedCadenceID(type: type, evmID: id) { let nft <- FlowEVMBridgeNFTEscrow.unlockNFT(type: type, id: cadenceID) @@ -360,9 +329,13 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { return <-nft } - // Otherwise, we expect the NFT to be minted in Cadence + + /* Mint bridge-defined NFT */ + // + // Ensure the NFT is bridge-defined assert(self.account.address == contractAddress, message: "Unexpected error bridging NFT from EVM") + // We expect the NFT to be minted in Cadence as it is bridge-defined let nft <- nftContract!.mintNFT(id: id, tokenURI: uri) return <-nft } diff --git a/cadence/contracts/bridge/FlowEVMBridgeUtils.cdc b/cadence/contracts/bridge/FlowEVMBridgeUtils.cdc index c2695a5e..7b1da8ae 100644 --- a/cadence/contracts/bridge/FlowEVMBridgeUtils.cdc +++ b/cadence/contracts/bridge/FlowEVMBridgeUtils.cdc @@ -408,6 +408,31 @@ contract FlowEVMBridgeUtils { return false } + /// Returns whether the given ERC721 exists, assuming the ERC721 contract implements the `exists` method. While this + /// method is not part of the ERC721 standard, it is implemented in the bridge-deployed ERC721 implementation. + /// Reverts on EVM call failure. + /// + /// @param erc721Address: The EVM contract address of the ERC721 token + /// @param id: The ID of the ERC721 token to check + /// + /// @return true if the ERC721 token exists, false otherwise + /// + access(all) + fun erc721Exists(erc721Address: EVM.EVMAddress, id: UInt256): Bool { + let existsResponse = EVM.decodeABI( + types: [Type()], + data: FlowEVMBridgeUtils.call( + signature: "exists(uint256)", + targetEVMAddress: erc721Address, + args: [id], + gasLimit: 12000000, + value: 0.0 + ).data, + ) + assert(existsResponse.length == 1, message: "Invalid response length") + return existsResponse[0] as! Bool + } + /// Returns the ERC20 balance of the owner at the given ERC20 contract address. Reverts on EVM call failure. /// /// @param amount: The amount to check if the owner has enough balance to cover @@ -756,6 +781,95 @@ contract FlowEVMBridgeUtils { ) } + /// Executes a safeTransferFrom call on the given ERC721 contract address, transferring the NFT from bridge escrow + /// in EVM to the named recipient and asserting pre- and post-state changes. + /// + access(account) + fun mustSafeTransferERC721(erc721Address: EVM.EVMAddress, to: EVM.EVMAddress, id: UInt256) { + let bridgeCOAAddress = self.getBridgeCOAEVMAddress() + + let bridgePreStatus = self.isOwner(ofNFT: id, owner: bridgeCOAAddress, evmContractAddress: erc721Address) + let toPreStatus = self.isOwner(ofNFT: id, owner: to, evmContractAddress: erc721Address) + assert(bridgePreStatus, message: "Bridge COA does not own NFT") + assert(!toPreStatus, message: "Recipient already owns NFT") + + let transferResult: EVM.Result = FlowEVMBridgeUtils.call( + signature: "safeTransferFrom(address,address,uint256)", + targetEVMAddress: erc721Address, + args: [bridgeCOAAddress, to, id], + gasLimit: 15000000, + value: 0.0 + ) + assert(transferResult.status == EVM.Status.successful, message: "Transfer to bridge recipient failed") + + let bridgePostStatus = self.isOwner(ofNFT: id, owner: bridgeCOAAddress, evmContractAddress: erc721Address) + let toPostStatus = self.isOwner(ofNFT: id, owner: to, evmContractAddress: erc721Address) + assert(!bridgePostStatus, message: "Bridge is still owner of NFT after transfer") + assert(toPostStatus, message: "Recipient does not own the NFT after transfer") + } + + /// Executes a safeMint call on the given ERC721 contract address, minting an ERC72 to the named recipient and + /// asserting pre- and post-state changes. Assumes the bridge COA has the authority to mint the NFT. + /// + access(account) + fun mustSafeMintERC721(erc721Address: EVM.EVMAddress, to: EVM.EVMAddress, id: UInt256, uri: String) { + let bridgeCOAAddress = self.getBridgeCOAEVMAddress() + + let mintResult: EVM.Result = FlowEVMBridgeUtils.call( + signature: "safeMint(address,uint256,string)", + targetEVMAddress: erc721Address, + args: [to, id, uri], + gasLimit: 15000000, + value: 0.0 + ) + assert(mintResult.status == EVM.Status.successful, message: "Mint to bridge recipient failed") + + let toPostStatus = self.isOwner(ofNFT: id, owner: to, evmContractAddress: erc721Address) + assert(toPostStatus, message: "Recipient does not own the NFT after minting") + } + + /// Executes updateTokenURI call on the given ERC721 contract address, updating the tokenURI of the NFT. This is + /// not a standard ERC721 function, but is implemented in the bridge-deployed ERC721 implementation to enable + /// synchronization of token metadata with Cadence NFT state on bridging. + /// + access(account) + fun mustUpdateTokenURI(erc721Address: EVM.EVMAddress, id: UInt256, uri: String) { + let bridgeCOAAddress = self.getBridgeCOAEVMAddress() + + let updateResult: EVM.Result = FlowEVMBridgeUtils.call( + signature: "updateTokenURI(uint256,string)", + targetEVMAddress: erc721Address, + args: [id, uri], + gasLimit: 15000000, + value: 0.0 + ) + assert(updateResult.status == EVM.Status.successful, message: "URI update failed") + } + + /// Executes the provided method, assumed to be a protected transfer call, and confirms that the transfer was + /// successful by validating the named owner is authorized to act on the NFT before the transfer, the transfer + /// was successful, and the bridge COA owns the NFT after the protected transfer call. + /// + access(account) + fun mustExecuteERC721ProtectedTransferCall( + owner: EVM.EVMAddress, + id: UInt256, + erc721Address: EVM.EVMAddress, + protectedTransferCall: fun (): EVM.Result + ) { + // Ensure the named owner is authorized to act on the NFT + let isAuthorized = self.isOwnerOrApproved(ofNFT: id, owner: owner, evmContractAddress: erc721Address) + assert(isAuthorized, message: "Named owner is not the owner of the NFT") + + // Call the protected transfer function which should execute a transfer call from the owner to escrow + let transferResult = protectedTransferCall() + assert(transferResult.status == EVM.Status.successful, message: "Transfer to escrow via callback failed") + + // Validate the NFT is now owned by the bridge COA, escrow the NFT + let isEscrowed = self.isOwner(ofNFT: id, owner: self.getBridgeCOAEVMAddress(), evmContractAddress: erc721Address) + assert(isEscrowed, message: "Bridge COA does not own NFT after transfer") + } + /// Mints ERC20 tokens to the recipient and confirms that the recipient's balance was updated /// access(account) From 5afa1ea624035509f25d7bb72fb245e152177efe Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Thu, 25 Apr 2024 16:01:02 -0500 Subject: [PATCH 4/6] rename Utils methods --- cadence/contracts/bridge/FlowEVMBridge.cdc | 4 ++-- cadence/contracts/bridge/FlowEVMBridgeHandlers.cdc | 2 +- cadence/contracts/bridge/FlowEVMBridgeUtils.cdc | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cadence/contracts/bridge/FlowEVMBridge.cdc b/cadence/contracts/bridge/FlowEVMBridge.cdc index cfedf58c..d0963dd9 100644 --- a/cadence/contracts/bridge/FlowEVMBridge.cdc +++ b/cadence/contracts/bridge/FlowEVMBridge.cdc @@ -300,7 +300,7 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { let associatedAddress = FlowEVMBridgeConfig.getEVMAddressAssociated(with: type) ?? panic("No EVMAddress found for token type") // Execute the transfer call and make needed state assertions to confirm escrow from named owner - FlowEVMBridgeUtils.mustExecuteERC721ProtectedTransferCall( + FlowEVMBridgeUtils.mustEscrowERC721( owner: owner, id: id, erc721Address: associatedAddress, @@ -490,7 +490,7 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge { /* Execute the transfer call and make needed state assertions */ // - FlowEVMBridgeUtils.mustExecuteProtectedERC20TransferCall( + FlowEVMBridgeUtils.mustEscrowERC20( owner: owner, amount: amount, erc20Address: associatedAddress, diff --git a/cadence/contracts/bridge/FlowEVMBridgeHandlers.cdc b/cadence/contracts/bridge/FlowEVMBridgeHandlers.cdc index 17306521..b39c10dc 100644 --- a/cadence/contracts/bridge/FlowEVMBridgeHandlers.cdc +++ b/cadence/contracts/bridge/FlowEVMBridgeHandlers.cdc @@ -135,7 +135,7 @@ access(all) contract FlowEVMBridgeHandlers { erc20Address: evmAddress ) - FlowEVMBridgeUtils.mustExecuteProtectedERC20TransferCall( + FlowEVMBridgeUtils.mustEscrowERC20( owner: owner, amount: amount, erc20Address: evmAddress, diff --git a/cadence/contracts/bridge/FlowEVMBridgeUtils.cdc b/cadence/contracts/bridge/FlowEVMBridgeUtils.cdc index 7b1da8ae..7d212eae 100644 --- a/cadence/contracts/bridge/FlowEVMBridgeUtils.cdc +++ b/cadence/contracts/bridge/FlowEVMBridgeUtils.cdc @@ -851,7 +851,7 @@ contract FlowEVMBridgeUtils { /// was successful, and the bridge COA owns the NFT after the protected transfer call. /// access(account) - fun mustExecuteERC721ProtectedTransferCall( + fun mustEscrowERC721( owner: EVM.EVMAddress, id: UInt256, erc721Address: EVM.EVMAddress, @@ -936,7 +936,7 @@ contract FlowEVMBridgeUtils { /// escrow balance was incremented by the same amount. /// access(account) - fun mustExecuteProtectedERC20TransferCall( + fun mustEscrowERC20( owner: EVM.EVMAddress, amount: UInt256, erc20Address: EVM.EVMAddress, From 016d61c1a717748b8df3a0178d09d40799f388de Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Thu, 25 Apr 2024 16:01:24 -0500 Subject: [PATCH 5/6] Update cadence/contracts/bridge/FlowEVMBridgeUtils.cdc Co-authored-by: Joshua Hannan --- cadence/contracts/bridge/FlowEVMBridgeUtils.cdc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cadence/contracts/bridge/FlowEVMBridgeUtils.cdc b/cadence/contracts/bridge/FlowEVMBridgeUtils.cdc index 7d212eae..eb143d79 100644 --- a/cadence/contracts/bridge/FlowEVMBridgeUtils.cdc +++ b/cadence/contracts/bridge/FlowEVMBridgeUtils.cdc @@ -808,7 +808,7 @@ contract FlowEVMBridgeUtils { assert(toPostStatus, message: "Recipient does not own the NFT after transfer") } - /// Executes a safeMint call on the given ERC721 contract address, minting an ERC72 to the named recipient and + /// Executes a safeMint call on the given ERC721 contract address, minting an ERC721 to the named recipient and /// asserting pre- and post-state changes. Assumes the bridge COA has the authority to mint the NFT. /// access(account) From ae75dffc6e6c3fd11c7614397597881a19c21b84 Mon Sep 17 00:00:00 2001 From: Giovanni Sanchez <108043524+sisyphusSmiling@users.noreply.github.com> Date: Thu, 25 Apr 2024 16:19:47 -0500 Subject: [PATCH 6/6] update error messages --- .../contracts/bridge/FlowEVMBridgeUtils.cdc | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/cadence/contracts/bridge/FlowEVMBridgeUtils.cdc b/cadence/contracts/bridge/FlowEVMBridgeUtils.cdc index eb143d79..52d83196 100644 --- a/cadence/contracts/bridge/FlowEVMBridgeUtils.cdc +++ b/cadence/contracts/bridge/FlowEVMBridgeUtils.cdc @@ -790,8 +790,8 @@ contract FlowEVMBridgeUtils { let bridgePreStatus = self.isOwner(ofNFT: id, owner: bridgeCOAAddress, evmContractAddress: erc721Address) let toPreStatus = self.isOwner(ofNFT: id, owner: to, evmContractAddress: erc721Address) - assert(bridgePreStatus, message: "Bridge COA does not own NFT") - assert(!toPreStatus, message: "Recipient already owns NFT") + assert(bridgePreStatus, message: "Bridge COA does not own ERC721 requesting to be transferred") + assert(!toPreStatus, message: "Recipient already owns ERC721 attempting to be transferred") let transferResult: EVM.Result = FlowEVMBridgeUtils.call( signature: "safeTransferFrom(address,address,uint256)", @@ -800,12 +800,15 @@ contract FlowEVMBridgeUtils { gasLimit: 15000000, value: 0.0 ) - assert(transferResult.status == EVM.Status.successful, message: "Transfer to bridge recipient failed") + assert( + transferResult.status == EVM.Status.successful, + message: "safeTransferFrom call to ERC721 transferring NFT from escrow to bridge recipient failed" + ) let bridgePostStatus = self.isOwner(ofNFT: id, owner: bridgeCOAAddress, evmContractAddress: erc721Address) let toPostStatus = self.isOwner(ofNFT: id, owner: to, evmContractAddress: erc721Address) - assert(!bridgePostStatus, message: "Bridge is still owner of NFT after transfer") - assert(toPostStatus, message: "Recipient does not own the NFT after transfer") + assert(!bridgePostStatus, message: "ERC721 is still in escrow after transfer") + assert(toPostStatus, message: "ERC721 was not successfully transferred to recipient from escrow") } /// Executes a safeMint call on the given ERC721 contract address, minting an ERC721 to the named recipient and @@ -859,15 +862,15 @@ contract FlowEVMBridgeUtils { ) { // Ensure the named owner is authorized to act on the NFT let isAuthorized = self.isOwnerOrApproved(ofNFT: id, owner: owner, evmContractAddress: erc721Address) - assert(isAuthorized, message: "Named owner is not the owner of the NFT") + assert(isAuthorized, message: "Named owner is not the owner of the ERC721") // Call the protected transfer function which should execute a transfer call from the owner to escrow let transferResult = protectedTransferCall() - assert(transferResult.status == EVM.Status.successful, message: "Transfer to escrow via callback failed") + assert(transferResult.status == EVM.Status.successful, message: "Transfer ERC721 to escrow via callback failed") // Validate the NFT is now owned by the bridge COA, escrow the NFT let isEscrowed = self.isOwner(ofNFT: id, owner: self.getBridgeCOAEVMAddress(), evmContractAddress: erc721Address) - assert(isEscrowed, message: "Bridge COA does not own NFT after transfer") + assert(isEscrowed, message: "ERC721 was not successfully escrowed") } /// Mints ERC20 tokens to the recipient and confirms that the recipient's balance was updated @@ -883,12 +886,12 @@ contract FlowEVMBridgeUtils { gasLimit: 15000000, value: 0.0 ) - assert(mintResult.status == EVM.Status.successful, message: "Tranfer to bridge recipient failed") + assert(mintResult.status == EVM.Status.successful, message: "Mint to bridge ERC20 contract failed") // Ensure bridge to recipient was succcessful let toPostBalance = FlowEVMBridgeUtils.balanceOf(owner: to, evmContractAddress: erc20Address) assert( toPostBalance == toPreBalance + amount, - message: "Transfer to bridge recipient failed" + message: "Recipient didn't receive minted ERC20 tokens during bridging" ) } @@ -905,15 +908,15 @@ contract FlowEVMBridgeUtils { evmContractAddress: erc20Address ) - // Mint tokens to the recipient - let mintResult: EVM.Result = FlowEVMBridgeUtils.call( + // Transfer tokens to the recipient + let transferResult: EVM.Result = FlowEVMBridgeUtils.call( signature: "transfer(address,uint256)", targetEVMAddress: erc20Address, args: [to, amount], gasLimit: 15000000, value: 0.0 ) - assert(mintResult.status == EVM.Status.successful, message: "Tranfer to bridge recipient failed") + assert(transferResult.status == EVM.Status.successful, message: "transfer call to ERC20 contract failed") // Ensure bridge to recipient was succcessful let toPostBalance = FlowEVMBridgeUtils.balanceOf(owner: to, evmContractAddress: erc20Address) @@ -923,11 +926,11 @@ contract FlowEVMBridgeUtils { ) assert( toPostBalance == toPreBalance + amount, - message: "Transfer to bridge COA failed - cannot bridge FT without bridge escrow" + message: "Recipient's ERC20 balance did not increment by the requested amount after transfer from escrow" ) assert( escrowPostBalance == escrowPreBalance - amount, - message: "Transfer to bridge COA failed - cannot bridge FT without bridge escrow" + message: "Escrow ERC20 balance did not decrement by the requested amount after transfer from escrow" ) }