Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add special case FungibleToken handler #46

Merged
merged 19 commits into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
c60de5b
add FlowEVMBridgeHandlerInterfaces
sisyphusSmiling Apr 19, 2024
7298004
update Handler interfaces & implement in bridge config
sisyphusSmiling Apr 22, 2024
636c7f4
implement CadenceNativeTokenHandler
sisyphusSmiling Apr 22, 2024
f0e9d0d
fix Handler & config pre-conditions and integrate Handlers into bridge
sisyphusSmiling Apr 23, 2024
97aa2cd
add Handler test coverage
sisyphusSmiling Apr 23, 2024
f868c3d
fix handler tests
sisyphusSmiling Apr 23, 2024
d2c8366
update coverge normalization script
sisyphusSmiling Apr 23, 2024
2338351
refactor FlowEVMBridgeUtils.deposit to .depositFee to ease internal f…
sisyphusSmiling Apr 23, 2024
9fc8926
add Handler logic to .bridgeTokensFromEVM
sisyphusSmiling Apr 23, 2024
602b6ba
add doc comments to handler contracts
sisyphusSmiling Apr 23, 2024
adc4e54
update FlowEVMBridgeConfig handler interfaces & implementations + com…
sisyphusSmiling Apr 23, 2024
5cb0a4d
update FlowEVMBridgeConfig comments
sisyphusSmiling Apr 23, 2024
4fdd973
add totalSupply util method & script
sisyphusSmiling Apr 23, 2024
99ec218
add supporting txns, scripts & test coverage for TokenHandler integra…
sisyphusSmiling Apr 23, 2024
3ed9b69
generalize create_cadence_native_token_handler
sisyphusSmiling Apr 23, 2024
15dc839
Merge branch 'main' into add-special-case-handler
sisyphusSmiling Apr 24, 2024
3f5d19e
refactor TokenHandler Minter setting pattern
sisyphusSmiling Apr 24, 2024
669a80f
reorganize admin transactions
sisyphusSmiling Apr 24, 2024
306640a
harden balance state assertions for token bridging
sisyphusSmiling Apr 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 42 additions & 26 deletions cadence/contracts/bridge/FlowEVMBridge.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import "IFlowEVMTokenBridge"
import "CrossVMNFT"
import "CrossVMToken"
import "FlowEVMBridgeConfig"
import "FlowEVMBridgeHandlerInterfaces"
import "FlowEVMBridgeUtils"
import "FlowEVMBridgeNFTEscrow"
import "FlowEVMBridgeTokenEscrow"
Expand Down Expand Up @@ -107,8 +108,7 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge {
message: "This type is not supported as defined by the project's development team"
)
// Withdraw from feeProvider and deposit to self
let feeVault <-feeProvider.withdraw(amount: FlowEVMBridgeConfig.onboardFee) as! @FlowToken.Vault
FlowEVMBridgeUtils.deposit(<-feeVault)
FlowEVMBridgeUtils.depositFee(feeProvider, feeAmount: FlowEVMBridgeConfig.onboardFee)
// Deploy an EVM defining contract via the FlowBridgeFactory.sol contract
let onboardingValues = self.deployEVMContract(forAssetType: type)
// Initialize bridge escrow for the asset
Expand Down Expand Up @@ -170,9 +170,8 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge {
self.evmAddressRequiresOnboarding(address) == true,
message: "Onboarding is not needed for this contract"
)
// Withdraw from feeProvider and deposit to self
let feeVault <-feeProvider.withdraw(amount: FlowEVMBridgeConfig.onboardFee) as! @FlowToken.Vault
FlowEVMBridgeUtils.deposit(<-feeVault)
// Withdraw fee from feeProvider and deposit
FlowEVMBridgeUtils.depositFee(feeProvider, feeAmount: FlowEVMBridgeConfig.onboardFee)
// Deploy a defining Cadence contract to the bridge account
self.deployDefiningContract(evmContractAddress: address)
}
Expand Down Expand Up @@ -215,13 +214,8 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge {
let storageUsed = FlowEVMBridgeNFTEscrow.lockNFT(<-token)
// Calculate the bridge fee on current rates
let feeAmount = FlowEVMBridgeUtils.calculateBridgeFee(bytes: storageUsed)
assert(
feeProvider.isAvailableToWithdraw(amount: feeAmount),
message: "Fee provider does not have balance to cover the bridge fee of ".concat(feeAmount.toString())
)
// Withdraw from feeProvider and deposit to self
let feeVault <-feeProvider.withdraw(amount: feeAmount) as! @FlowToken.Vault
FlowEVMBridgeUtils.deposit(<-feeVault)
// Withdraw fee from feeProvider and deposit
FlowEVMBridgeUtils.depositFee(feeProvider, feeAmount: feeAmount)

// Does the bridge control the EVM contract associated with this type?
let associatedAddress = FlowEVMBridgeConfig.getEVMAddressAssociated(with: tokenType)
Expand Down Expand Up @@ -316,8 +310,7 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge {
}
// Withdraw from feeProvider and deposit to self
let feeAmount = FlowEVMBridgeUtils.calculateBridgeFee(bytes: 0)
let feeVault <-feeProvider.withdraw(amount: feeAmount) as! @FlowToken.Vault
FlowEVMBridgeUtils.deposit(<-feeVault)
FlowEVMBridgeUtils.depositFee(feeProvider, feeAmount: feeAmount)

// Get the EVMAddress of the ERC721 contract associated with the type
let associatedAddress = FlowEVMBridgeConfig.getEVMAddressAssociated(with: type)
Expand Down Expand Up @@ -396,25 +389,34 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge {

let vaultBalance = vault.balance
var feeAmount = 0.0
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
feeAmount = FlowEVMBridgeUtils.calculateBridgeFee(bytes: 0)
FlowEVMBridgeUtils.depositFee(feeProvider, feeAmount: feeAmount)
return
}

// Lock the tokens if the bridge does not define them
if FlowEVMBridgeUtils.isCadenceNative(type: vault.getType()) {
// 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
let storageUsed = FlowEVMBridgeTokenEscrow.lockTokens(<-vault)
// Calculate the bridge fee on current rates
feeAmount = FlowEVMBridgeUtils.calculateBridgeFee(bytes: storageUsed)
} else {
// Since not Cadence-native, bridge defines the token - burn the vault and calculate the fee
Burner.burn(<-vault)
feeAmount = FlowEVMBridgeUtils.calculateBridgeFee(bytes: 0)
}

// Withdraw from feeProvider and deposit to self
assert(
feeProvider.isAvailableToWithdraw(amount: feeAmount),
message: "Fee provider does not have balance to cover the bridge fee of ".concat(feeAmount.toString())
)
let feeVault <-feeProvider.withdraw(amount: feeAmount) as! @FlowToken.Vault
FlowEVMBridgeUtils.deposit(<-feeVault)
// Withdraw fee amount from feeProvider and deposit
FlowEVMBridgeUtils.depositFee(feeProvider, feeAmount: feeAmount)

// Does the bridge control the EVM contract associated with this type?
let associatedAddress = FlowEVMBridgeConfig.getEVMAddressAssociated(with: vaultType)
Expand Down Expand Up @@ -478,8 +480,21 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge {
}
// Withdraw from feeProvider and deposit to self
let feeAmount = FlowEVMBridgeUtils.calculateBridgeFee(bytes: 0)
let feeVault <-feeProvider.withdraw(amount: feeAmount) as! @FlowToken.Vault
FlowEVMBridgeUtils.deposit(<-feeVault)
FlowEVMBridgeUtils.depositFee(feeProvider, feeAmount: feeAmount)

if FlowEVMBridgeConfig.typeHasTokenHandler(type) {
// Some tokens pre-dating bridge require special case handling - 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,
amount: amount,
protectedTransferCall: protectedTransferCall
)
}

// Get the EVMAddress of the ERC20 contract associated with the type
let associatedAddress = FlowEVMBridgeConfig.getEVMAddressAssociated(with: type)
Expand Down Expand Up @@ -582,7 +597,8 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge {
if !FlowEVMBridgeUtils.isValidFlowAsset(type: type) {
return nil
}
return FlowEVMBridgeConfig.getEVMAddressAssociated(with: type) == nil
return FlowEVMBridgeConfig.getEVMAddressAssociated(with: type) == nil &&
!FlowEVMBridgeConfig.typeHasTokenHandler(type)
}

/// Returns whether an EVM-native asset needs to be onboarded to the bridge
Expand Down
163 changes: 154 additions & 9 deletions cadence/contracts/bridge/FlowEVMBridgeConfig.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,23 @@ import "EVM"
import "FlowToken"

import "EVMUtils"
import "FlowEVMBridgeHandlerInterfaces"

/// This contract is used to store configuration information shared by FlowEVMBridge contracts
///
access(all)
contract FlowEVMBridgeConfig {

/* --- Contract values --- */
//
/******************
Entitlements
*******************/

access(all) entitlement Fee

/*************
Fields
**************/

/// Amount of FLOW paid to onboard a Type or EVMAddress to the bridge
access(all)
var onboardFee: UFix64
Expand All @@ -27,24 +36,35 @@ contract FlowEVMBridgeConfig {
/// as of contract development is not a hashable or equatable type and making it so is not supported by Cadence
access(self)
let evmAddressHexToType: {String: Type}
/// Mapping of Type to its associated EVMAddress as relevant to the bridge
access(self)
let typeToTokenHandlers: @{Type: {FlowEVMBridgeHandlerInterfaces.TokenHandler}}

/********************
Path Constants
*********************/

/* --- Path Constants --- */
//
/// StoragePath where bridge Cadence Owned Account is stored
access(all)
let coaStoragePath: StoragePath
/// StoragePath where bridge config Admin is stored
access(all)
let adminStoragePath: StoragePath
/// StoragePath to store the Provider capability used as a bridge fee Provider
access(all)
let providerCapabilityStoragePath: StoragePath

/* --- Events --- */
//
/*************
Events
**************/

/// Emitted whenever the onboarding fee is updated
///
access(all)
event BridgeFeeUpdated(old: UFix64, new: UFix64, isOnboarding: Bool)
// TODO
access(all)
event HandlerConfigured(targetType: Type, targetEVMAddress: String?, isEnabled: Bool)

/*************
Getters
Expand Down Expand Up @@ -78,6 +98,72 @@ contract FlowEVMBridgeConfig {
self.evmAddressHexToType[evmAddressHex] = type
}

/// Returns whether the given Type has a TokenHandler configured
///
access(account)
view fun typeHasTokenHandler(_ type: Type): Bool {
return self.typeToTokenHandlers[type] != nil
}

/// Returns whether the given EVMAddress has a TokenHandler configured
///
access(account)
view fun evmAddressHasTokenHandler(_ evmAddress: EVM.EVMAddress): Bool {
let associatedType = self.getTypeAssociated(with: evmAddress)
return associatedType == nil ? self.typeHasTokenHandler(associatedType!) : false
}

/// Adds a TokenHandler to the bridge configuration
///
access(account)
fun addTokenHandler(_ handler: @{FlowEVMBridgeHandlerInterfaces.TokenHandler}) {
pre {
handler.getTargetType() != nil: "Cannot configure Handler without a target Cadence Type set"
self.getEVMAddressAssociated(with: handler.getTargetType()!) == nil:
"Cannot configure Handler for Type that has already been onboarded to the bridge"
self.borrowTokenHandler(handler.getTargetType()!) == nil:
"Cannot configure Handler for Type that already has a Handler configured"
}
let type = handler.getTargetType()!
var targetEVMAddressHex: String? = nil
if let targetEVMAddress = handler.getTargetEVMAddress() {
targetEVMAddressHex = EVMUtils.getEVMAddressAsHexString(address: targetEVMAddress)

let associatedType = self.getTypeAssociated(with: targetEVMAddress)
assert(
associatedType == nil,
message: "Handler target EVMAddress is already associated with a different Type"
)
self.associateType(type, with: targetEVMAddress)
}

emit HandlerConfigured(
targetType: type,
targetEVMAddress: targetEVMAddressHex,
isEnabled: handler.isEnabled()
)

self.typeToTokenHandlers[type] <-! handler
}

/// Returns an unentitled reference to the TokenHandler associated with the given Type
///
access(account)
view fun borrowTokenHandler(
_ type: Type
): &{FlowEVMBridgeHandlerInterfaces.TokenHandler}? {
return &self.typeToTokenHandlers[type]
}

/// Returns an entitled reference to the TokenHandler associated with the given Type
///
access(self)
view fun borrowTokenHandlerAdmin(
_ type: Type
): auth(FlowEVMBridgeHandlerInterfaces.Admin) &{FlowEVMBridgeHandlerInterfaces.TokenHandler}? {
return &self.typeToTokenHandlers[type]
}

/*****************
Config Admin
*****************/
Expand All @@ -93,7 +179,7 @@ contract FlowEVMBridgeConfig {
///
/// @emits BridgeFeeUpdated with the old and new rates and isOnboarding set to true
///
access(all)
access(Fee)
fun updateOnboardingFee(_ new: UFix64) {
emit BridgeFeeUpdated(old: FlowEVMBridgeConfig.onboardFee, new: new, isOnboarding: true)
FlowEVMBridgeConfig.onboardFee = new
Expand All @@ -105,11 +191,68 @@ contract FlowEVMBridgeConfig {
///
/// @emits BridgeFeeUpdated with the old and new rates and isOnboarding set to false
///
access(all)
access(Fee)
fun updateBaseFee(_ new: UFix64) {
emit BridgeFeeUpdated(old: FlowEVMBridgeConfig.baseFee, new: new, isOnboarding: false)
FlowEVMBridgeConfig.baseFee = new
}

/// Sets the target EVM contract address on the handler for a given Type, associating the Cadence type with the
/// provided EVM address. If a TokenHandler does not exist for the given Type, the operation reverts.
///
/// @param targetType: Cadence type to associate with the target EVM address
/// @param targetEVMAddress: target EVM address to associate with the Cadence type
///
/// @emits HandlerConfigured with the target Type, target EVM address, and whether the handler is enabled
///
access(FlowEVMBridgeHandlerInterfaces.Admin)
fun setHandlerTargetEVMAddress(targetType: Type, targetEVMAddress: EVM.EVMAddress) {
pre {
FlowEVMBridgeConfig.getTypeAssociated(with: targetEVMAddress) == nil:
"EVM Address already associated with another Type"
}
let handler = FlowEVMBridgeConfig.borrowTokenHandlerAdmin(targetType)
?? panic("No handler found for target Type")
handler.setTargetEVMAddress(targetEVMAddress)

if FlowEVMBridgeConfig.getEVMAddressAssociated(with: targetType) == nil {
FlowEVMBridgeConfig.associateType(targetType, with: targetEVMAddress)
}
assert(
FlowEVMBridgeConfig.getEVMAddressAssociated(with: targetType)!.bytes == targetEVMAddress.bytes,
message: "Problem associating target Type and target EVM Address"
)

emit HandlerConfigured(
targetType: targetType,
targetEVMAddress: EVMUtils.getEVMAddressAsHexString(address: targetEVMAddress),
isEnabled: handler.isEnabled()
)
}

/// Enables the TokenHandler for the given Type. If a TokenHandler does not exist for the given Type, the
/// operation reverts.
///
/// @param targetType: Cadence type indexing the relevant TokenHandler
///
/// @emits HandlerConfigured with the target Type, target EVM address, and whether the handler is enabled
///
access(FlowEVMBridgeHandlerInterfaces.Admin)
fun enableHandler(targetType: Type) {
let handler = FlowEVMBridgeConfig.borrowTokenHandlerAdmin(targetType)
?? panic("No handler found for target Type")
handler.enableBridging()

let targetEVMAddressHex = EVMUtils.getEVMAddressAsHexString(
address: handler.getTargetEVMAddress() ?? panic("Handler cannot be enabled without a target EVM Address")
)

emit HandlerConfigured(
targetType: handler.getTargetType()!,
targetEVMAddress: targetEVMAddressHex,
isEnabled: handler.isEnabled()
)
}
}

init() {
Expand All @@ -125,7 +268,9 @@ contract FlowEVMBridgeConfig {
let flowVaultType = Type<@FlowToken.Vault>()
let flowOriginationAddressHex = EVMUtils.getEVMAddressAsHexString(address: flowOriginationAddress)
self.typeToEVMAddress = { flowVaultType: flowOriginationAddress }
self.evmAddressHexToType = { flowOriginationAddressHex: flowVaultType}
self.evmAddressHexToType = { flowOriginationAddressHex: flowVaultType }
self.typeToTokenHandlers <- {}

self.adminStoragePath = /storage/flowEVMBridgeConfigAdmin
self.coaStoragePath = /storage/evm
self.providerCapabilityStoragePath = /storage/bridgeFlowVaultProvider
Expand Down
Loading
Loading