Skip to content

Commit

Permalink
Merge pull request #19 from onflow/add-bridge-interface
Browse files Browse the repository at this point in the history
Add NFT bridge interface
  • Loading branch information
sisyphusSmiling authored Apr 3, 2024
2 parents 0eaeb66 + 016be1b commit 5ebf96c
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 54 deletions.
50 changes: 41 additions & 9 deletions cadence/contracts/bridge/EVMBridgeRouter.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,33 @@ import "FlowToken"

import "EVM"

import "FlowEVMBridgeConfig"
import "FlowEVMBridge"
import "FlowEVMBridgeNFTEscrow"
import "IFlowEVMNFTBridge"

/// This contract defines a mechanism for routing bridge requests from the EVM contract to the Flow-EVM bridge as well
/// as updating the designated bridge address
///
access(all)
contract EVMBridgeRouter {

/// Entitlement allowing for updates to the Router
access(all) entitlement RouterAdmin

/// Emitted if/when the bridge contract the router directs to is updated
access(all) event BridgeContractUpdated(address: Address, name: String)

/// BridgeAccessor implementation used by the EVM contract to route bridge calls between VMs
///
access(all)
resource Router : EVM.BridgeAccessor {
/// Address of the bridge contract
access(all) var bridgeAddress: Address
/// Name of the bridge contract
access(all) var bridgeContractName: String

init(address: Address, name: String) {
self.bridgeAddress = address
self.bridgeContractName = name
}

/// Passes along the bridge request to dedicated bridge contract
///
Expand All @@ -31,7 +44,7 @@ contract EVMBridgeRouter {
to: EVM.EVMAddress,
feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider}
) {
FlowEVMBridge.bridgeNFTToEVM(token: <-nft, to: to, feeProvider: feeProvider)
self.borrowBridge().bridgeNFTToEVM(token: <-nft, to: to, feeProvider: feeProvider)
}

/// Passes along the bridge request to the dedicated bridge contract, returning the bridged NFT
Expand All @@ -50,6 +63,7 @@ contract EVMBridgeRouter {
id: UInt256,
feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider}
): @{NonFungibleToken.NFT} {
let bridge = self.borrowBridge()
// Define a callback function, enabling the bridge to act on the ephemeral COA reference in scope
var executed = false
fun callback(): EVM.Result {
Expand All @@ -61,28 +75,46 @@ contract EVMBridgeRouter {
}
executed = true
return caller.call(
to: FlowEVMBridgeConfig.getEVMAddressAssociated(with: type)
to: bridge.getAssociatedEVMAddress(with: type)
?? panic("No EVM address associated with type"),
data: EVM.encodeABIWithSignature(
"safeTransferFrom(address,address,uint256)",
[caller.address(), FlowEVMBridge.getBridgeCOAEVMAddress(), id]
[caller.address(), bridge.getBridgeCOAEVMAddress(), id]
),
gasLimit: 15000000,
value: EVM.Balance(attoflow: 0)
)
}
// Execute the bridge request
return <- FlowEVMBridge.bridgeNFTFromEVM(
return <- bridge.bridgeNFTFromEVM(
owner: caller.address(),
type: type,
id: id,
feeProvider: feeProvider,
protectedTransferCall: callback
)
}

/// Sets the bridge contract the router directs bridge requests through
///
access(RouterAdmin) fun setBridgeContract(address: Address, name: String) {
self.bridgeAddress = address
self.bridgeContractName = name
emit BridgeContractUpdated(address: address, name: name)
}

/// Returns a reference to the bridge contract
///
access(self) fun borrowBridge(): &{IFlowEVMNFTBridge} {
return getAccount(self.bridgeAddress).contracts.borrow<&{IFlowEVMNFTBridge}>(name: self.bridgeContractName)
?? panic("Bridge contract not found")
}
}

init() {
self.account.storage.save(<-create Router(), to: /storage/evmBridgeRouter)
init(bridgeAddress: Address, bridgeContractName: String) {
self.account.storage.save(
<-create Router(address: bridgeAddress, name: bridgeContractName),
to: /storage/evmBridgeRouter
)
}
}
51 changes: 10 additions & 41 deletions cadence/contracts/bridge/FlowEVMBridge.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import "EVM"
import "BridgePermissions"
import "ICrossVM"
import "IEVMBridgeNFTMinter"
import "IFlowEVMNFTBridge"
import "CrossVMNFT"
import "FlowEVMBridgeConfig"
import "FlowEVMBridgeUtils"
Expand All @@ -25,7 +26,7 @@ import "FlowEVMBridgeTemplates"
/// - FLIP #237: https://github.com/onflow/flips/pull/233
///
access(all)
contract FlowEVMBridge {
contract FlowEVMBridge : IFlowEVMNFTBridge {

/*************
Events
Expand All @@ -43,24 +44,6 @@ contract FlowEVMBridge {
isERC721: Bool,
evmContractAddress: String
)
/// Broadcasts an NFT was bridged from Cadence to EVM
access(all)
event BridgedNFTToEVM(
type: Type,
id: UInt64,
evmID: UInt256,
to: String,
evmContractAddress: String
)
/// Broadcasts an NFT was bridged from EVM to Cadence
access(all)
event BridgedNFTFromEVM(
type: Type,
id: UInt64,
evmID: UInt256,
caller: String,
evmContractAddress: String
)

/**************************
Public NFT Handling
Expand Down Expand Up @@ -219,15 +202,8 @@ contract FlowEVMBridge {
gasLimit: 15000000,
value: 0.0
)
assert(callResult.status == EVM.Status.successful, message: "Tranfer to bridge recipient failed")
assert(callResult.status == EVM.Status.successful, message: "Transfer to bridge recipient failed")
}
emit BridgedNFTToEVM(
type: tokenType,
id: tokenID,
evmID: evmID,
to: FlowEVMBridgeUtils.getEVMAddressAsHexString(address: to),
evmContractAddress: FlowEVMBridgeUtils.getEVMAddressAsHexString(address:associatedAddress)
)
}

/// Public entrypoint to bridge NFTs from EVM to Cadence
Expand Down Expand Up @@ -287,13 +263,6 @@ contract FlowEVMBridge {
assert(isEscrowed, message: "Transfer to bridge COA failed - cannot bridge NFT without bridge escrow")
// If the NFT is currently locked, unlock and return
if let cadenceID = FlowEVMBridgeNFTEscrow.getLockedCadenceID(type: type, evmID: id) {
emit BridgedNFTFromEVM(
type: type,
id: cadenceID,
evmID: id,
caller: FlowEVMBridgeUtils.getEVMAddressAsHexString(address: owner),
evmContractAddress: FlowEVMBridgeUtils.getEVMAddressAsHexString(address: associatedAddress)
)
return <-FlowEVMBridgeNFTEscrow.unlockNFT(type: type, id: cadenceID)
}
// Otherwise, we expect the NFT to be minted in Cadence
Expand All @@ -304,13 +273,6 @@ contract FlowEVMBridge {
let nftContract = getAccount(contractAddress).contracts.borrow<&{IEVMBridgeNFTMinter}>(name: contractName)!
let uri = FlowEVMBridgeUtils.getTokenURI(evmContractAddress: associatedAddress, id: id)
let nft <- nftContract.mintNFT(id: id, tokenURI: uri)
emit BridgedNFTFromEVM(
type: type,
id: nft.id,
evmID: id,
caller: FlowEVMBridgeUtils.getEVMAddressAsHexString(address: owner),
evmContractAddress: FlowEVMBridgeUtils.getEVMAddressAsHexString(address: associatedAddress)
)
return <-nft
}

Expand All @@ -323,6 +285,13 @@ contract FlowEVMBridge {
Public Getters
**************************/

/// Returns the EVM address associated with the provided type
///
access(all)
view fun getAssociatedEVMAddress(with type: Type): EVM.EVMAddress? {
return FlowEVMBridgeConfig.getEVMAddressAssociated(with: type)
}

/// Retrieves the bridge contract's COA EVMAddress
///
/// @returns The EVMAddress of the bridge contract's COA orchestrating actions in FlowEVM
Expand Down
115 changes: 115 additions & 0 deletions cadence/contracts/bridge/IFlowEVMNFTBridge.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import "FungibleToken"
import "NonFungibleToken"

import "EVM"

import "FlowEVMBridgeConfig"
import "FlowEVMBridgeUtils"
import "CrossVMNFT"

access(all) contract interface IFlowEVMNFTBridge {

/*************
Events
**************/

/// Broadcasts an NFT was bridged from Cadence to EVM
access(all)
event BridgedNFTToEVM(
type: Type,
id: UInt64,
evmID: UInt256,
to: String,
evmContractAddress: String,
bridgeAddress: Address
)
/// Broadcasts an NFT was bridged from EVM to Cadence
access(all)
event BridgedNFTFromEVM(
type: Type,
id: UInt64,
evmID: UInt256,
caller: String,
evmContractAddress: String,
bridgeAddress: Address
)

/**************
Getters
***************/

/// Returns the EVM address associated with the provided type
///
access(all)
view fun getAssociatedEVMAddress(with type: Type): EVM.EVMAddress?

/// Returns the EVM address of the bridge coordinating COA
///
access(all)
view fun getBridgeCOAEVMAddress(): EVM.EVMAddress

/********************************
Public Bridge Entrypoints
*********************************/

/// Public entrypoint to bridge NFTs from Cadence to EVM.
///
/// @param token: The NFT to be bridged
/// @param to: The NFT recipient in FlowEVM
/// @param feeProvider: A reference to a FungibleToken Provider from which the bridging fee is withdrawn in $FLOW
///
access(all)
fun bridgeNFTToEVM(
token: @{NonFungibleToken.NFT},
to: EVM.EVMAddress,
feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider}
) {
pre {
emit BridgedNFTToEVM(
type: token.getType(),
id: token.id,
evmID: CrossVMNFT.getEVMID(from: &token as &{NonFungibleToken.NFT}) ?? UInt256(token.id),
to: FlowEVMBridgeUtils.getEVMAddressAsHexString(address: to),
evmContractAddress: FlowEVMBridgeUtils.getEVMAddressAsHexString(
address: self.getAssociatedEVMAddress(with: token.getType())
?? panic("Could not find EVM Contract address associated with provided NFT")
), bridgeAddress: self.account.address
)
}
}

/// Public entrypoint to bridge NFTs from EVM to Cadence
///
/// @param owner: The EVM address of the NFT owner. Current ownership and successful transfer (via
/// `protectedTransferCall`) is validated before the bridge request is executed.
/// @param calldata: Caller-provided approve() call, enabling contract COA to operate on NFT in EVM contract
/// @param id: The NFT ID to bridged
/// @param evmContractAddress: Address of the EVM address defining the NFT being bridged - also call target
/// @param feeProvider: A reference to a FungibleToken Provider from which the bridging fee is withdrawn in $FLOW
/// @param protectedTransferCall: A function that executes the transfer of the NFT from the named owner to the
/// bridge's COA. This function is expected to return a Result indicating the status of the transfer call.
///
/// @returns The bridged NFT
///
access(all)
fun bridgeNFTFromEVM(
owner: EVM.EVMAddress,
type: Type,
id: UInt256,
feeProvider: auth(FungibleToken.Withdraw) &{FungibleToken.Provider},
protectedTransferCall: fun (): EVM.Result
): @{NonFungibleToken.NFT} {
post {
emit BridgedNFTFromEVM(
type: result.getType(),
id: result.id,
evmID: id,
caller: FlowEVMBridgeUtils.getEVMAddressAsHexString(address: owner),
evmContractAddress: FlowEVMBridgeUtils.getEVMAddressAsHexString(
address: self.getAssociatedEVMAddress(with: result.getType())
?? panic("Could not find EVM Contract address associated with provided NFT")
), bridgeAddress: self.account.address
)
}
}
}
9 changes: 5 additions & 4 deletions local/setup_emulator.2.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Provided address is the address of the Factory contract deployed in the previous txn
flow-c1 accounts add-contract ./cadence/contracts/bridge/FlowEVMBridgeUtils.cdc \
<REPLACE WITH `deployedContractAddress` VALUE WHEN FACTORY WAS DEPLOYED>
<REPLACE WITH DEPLOYED FACTORY EVM ADDRESS>

flow-c1 accounts add-contract ./cadence/contracts/bridge/FlowEVMBridgeNFTEscrow.cdc
flow-c1 accounts add-contract ./cadence/contracts/bridge/FlowEVMBridgeTemplates.cdc
Expand All @@ -10,11 +10,12 @@ flow-c1 transactions send ./cadence/transactions/bridge/admin/upsert_contract_co

flow-c1 accounts add-contract ./cadence/contracts/bridge/IEVMBridgeNFTMinter.cdc

# Deploy main bridge contract
flow-c1 accounts add-contract ./cadence/contracts/bridge/FlowEVMBridge.cdc f8d6e0586b0a20c7
# Deploy main bridge interface & contract
flow-c1 accounts add-contract ./cadence/contracts/bridge/IFlowEVMNFTBridge.cdc
flow-c1 accounts add-contract ./cadence/contracts/bridge/FlowEVMBridge.cdc

# Deploy the bridge router directing calls from COAs to the dedicated bridge
flow-c1 accounts add-contract ./cadence/contracts/bridge/EVMBridgeRouter.cdc f8d6e0586b0a20c7
flow-c1 accounts add-contract ./cadence/contracts/bridge/EVMBridgeRouter.cdc 0xf8d6e0586b0a20c7 FlowEVMBridge

# Create `example-nft` account 179b6b1cb6755e31 with private key 96dfbadf086daa187100a24b1fd2b709b702954bbd030a394148e11bcbb799ef
flow-c1 accounts create --key "351e1310301a7374430f6077d7b1b679c9574f8e045234eac09568ceb15c4f5d937104b4c3180df1e416da20c9d58aac576ffc328a342198a5eae4a29a13c47a"
Expand Down

0 comments on commit 5ebf96c

Please sign in to comment.