Skip to content

Commit

Permalink
refactor TokenHandler Minter setting pattern
Browse files Browse the repository at this point in the history
  • Loading branch information
sisyphusSmiling committed Apr 24, 2024
1 parent 15dc839 commit 3f5d19e
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 27 deletions.
29 changes: 29 additions & 0 deletions cadence/contracts/bridge/FlowEVMBridgeConfig.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ contract FlowEVMBridgeConfig {
/// StoragePath where bridge config Admin is stored
access(all)
let adminStoragePath: StoragePath
/// PublicPath where a public Capability on the bridge config Admin is exposed
access(all)
let adminPublicPath: PublicPath
/// StoragePath to store the Provider capability used as a bridge fee Provider
access(all)
let providerCapabilityStoragePath: StoragePath
Expand Down Expand Up @@ -173,6 +176,26 @@ contract FlowEVMBridgeConfig {
access(all)
resource Admin {

/// Sets the TokenMinter for the given Type. If a TokenHandler does not exist for the given Type, the operation
/// reverts. The provided minter must be of the expected type for the TokenHandler and the handler cannot have
/// a minter already set.
///
/// @param targetType: Cadence type indexing the relevant TokenHandler
/// @param minter: TokenMinter minter to set for the TokenHandler
///
access(all)
fun setTokenHandlerMinter(targetType: Type, minter: @{FlowEVMBridgeHandlerInterfaces.TokenMinter}) {
pre {
FlowEVMBridgeConfig.typeHasTokenHandler(targetType):
"Cannot set minter for Type that does not have a TokenHandler configured"
}
let handler = FlowEVMBridgeConfig.borrowTokenHandlerAdmin(targetType)
?? panic("No handler found for target Type")
assert(minter.getType() == handler.getExpectedMinterType(), message: "Invalid minter type")

handler.setMinter(<-minter)
}

/// Updates the onboarding fee
///
/// @param new: UFix64 - new onboarding fee
Expand Down Expand Up @@ -259,6 +282,7 @@ contract FlowEVMBridgeConfig {
self.onboardFee = 0.0
self.baseFee = 0.0
self.defaultDecimals = 18

// Although $FLOW does not have ERC20 address, we associate the the Vault with the EVM address from which
// EVM transfers originate
// See FLIP #223 - https://github.com/onflow/flips/pull/225
Expand All @@ -269,12 +293,17 @@ contract FlowEVMBridgeConfig {
let flowOriginationAddressHex = EVMUtils.getEVMAddressAsHexString(address: flowOriginationAddress)
self.typeToEVMAddress = { flowVaultType: flowOriginationAddress }
self.evmAddressHexToType = { flowOriginationAddressHex: flowVaultType }

self.typeToTokenHandlers <- {}

self.adminStoragePath = /storage/flowEVMBridgeConfigAdmin
self.adminPublicPath = /public/flowEVMBridgeConfigAdmin
self.coaStoragePath = /storage/evm
self.providerCapabilityStoragePath = /storage/bridgeFlowVaultProvider

// Create & save Admin, issuing a public unentitled Admin Capability
self.account.storage.save(<-create Admin(), to: self.adminStoragePath)
let adminCap = self.account.capabilities.storage.issue<&Admin>(self.adminStoragePath)
self.account.capabilities.publish(adminCap, at: self.adminPublicPath)
}
}
10 changes: 9 additions & 1 deletion cadence/contracts/bridge/FlowEVMBridgeHandlerInterfaces.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ access(all) contract FlowEVMBridgeHandlerInterfaces {
access(all) view fun getTargetType(): Type?
/// Returns the EVM address handled by the Handler, nil if not set
access(all) view fun getTargetEVMAddress(): EVM.EVMAddress?
/// Returns the Type of the expected minter if the handler utilizes one
access(all) view fun getExpectedMinterType(): Type?
}

/// Administrative interface for Handler configuration
Expand All @@ -65,7 +67,13 @@ access(all) contract FlowEVMBridgeHandlerInterfaces {
self.getTargetEVMAddress()!.bytes == address!.bytes: "Problem setting target EVM address"
}
}
/// Enables the Handler to fulfill bridge requests for the configured targets
access(Admin) fun setMinter(_ minter: @{FlowEVMBridgeHandlerInterfaces.TokenMinter}) {
pre {
self.getExpectedMinterType() == minter.getType(): "Minter is not of the expected type"
}
}
/// Enables the Handler to fulfill bridge requests for the configured targets. If implementers utilize a minter,
/// they should additionally ensure the minter is set before enabling.
access(Admin) fun enableBridging() {
pre {
self.getTargetType() != nil && self.getTargetEVMAddress() != nil:
Expand Down
41 changes: 33 additions & 8 deletions cadence/contracts/bridge/FlowEVMBridgeHandlers.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,21 @@ access(all) contract FlowEVMBridgeHandlers {
/// contract address is not yet known but the Cadence type must still be filtered via Handler to prevent the
/// type from being onboarded otherwise.
access(self) var targetEVMAddress: EVM.EVMAddress?
/// The expected minter type for minting tokens on fulfillment
access(self) let expectedMinterType: Type
/// The Minter enabling minting of Cadence tokens on fulfillment from EVM
access(self) let minter: @{FlowEVMBridgeHandlerInterfaces.TokenMinter}
access(self) var minter: @{FlowEVMBridgeHandlerInterfaces.TokenMinter}?

init(targetType: Type, targetEVMAddress: EVM.EVMAddress?, minter: @{FlowEVMBridgeHandlerInterfaces.TokenMinter}) {
init(targetType: Type, targetEVMAddress: EVM.EVMAddress?, expectedMinterType: Type) {
pre {
expectedMinterType.isSubtype(of: Type<@{FlowEVMBridgeHandlerInterfaces.TokenMinter}>()):
"Invalid minter type"
}
self.enabled = false
self.targetType = targetType
self.targetEVMAddress = targetEVMAddress
self.minter <- minter
self.expectedMinterType = expectedMinterType
self.minter <- nil
}

/* --- HandlerInfo --- */
Expand All @@ -74,6 +81,11 @@ access(all) contract FlowEVMBridgeHandlers {
return self.targetEVMAddress
}

/// Returns the expected minter type for the handler
access(all) view fun getExpectedMinterType(): Type? {
return self.expectedMinterType
}

/* --- TokenHandler --- */

/// Fulfill a request to bridge tokens from Cadence to EVM, burning the provided Vault and transferring from
Expand Down Expand Up @@ -171,7 +183,8 @@ access(all) contract FlowEVMBridgeHandlers {
assert(bridgePostBalance == bridgePreBalance + amount, message: "Transfer to bridge escrow failed")

// After state confirmation, mint the tokens and return
let minted <- self.borrowMinter().mint(amount: ufixAmount)
let minter = self.borrowMinter() ?? panic("Minter not set")
let minted <- minter.mint(amount: ufixAmount)
return <-minted
}

Expand All @@ -189,17 +202,29 @@ access(all) contract FlowEVMBridgeHandlers {
self.targetEVMAddress = address
}

/// Sets the target type for the handler
access(FlowEVMBridgeHandlerInterfaces.Admin)
fun setMinter(_ minter: @{FlowEVMBridgeHandlerInterfaces.TokenMinter}) {
pre {
self.minter == nil: "Minter has already been set"
}
self.minter <-! minter
}

/// Enables the handler for request handling. The
access(FlowEVMBridgeHandlerInterfaces.Admin)
fun enableBridging() {
pre {
self.minter != nil: "Cannot enable handler without a minter"
}
self.enabled = true
}

/* --- Internal --- */

/// Returns an entitled reference to the encapsulated minter resource
access(self)
view fun borrowMinter(): auth(FlowEVMBridgeHandlerInterfaces.Mint) &{FlowEVMBridgeHandlerInterfaces.TokenMinter} {
view fun borrowMinter(): auth(FlowEVMBridgeHandlerInterfaces.Mint) &{FlowEVMBridgeHandlerInterfaces.TokenMinter}? {
return &self.minter
}
}
Expand All @@ -213,21 +238,21 @@ access(all) contract FlowEVMBridgeHandlers {
/// @param handlerType: The type of handler to create as defined in this contract
/// @param targetType: The type of the asset the handler will handle.
/// @param targetEVMAddress: The EVM contract address the handler will handle, can be nil if still unknown
/// @param minter: The minter resource to use for minting tokens on fulfillment
/// @param expectedMinterType: The Type of the expected minter to be set for the created TokenHandler
///
access(FlowEVMBridgeHandlerInterfaces.Admin)
fun createTokenHandler(
handlerType: Type,
targetType: Type,
targetEVMAddress: EVM.EVMAddress?,
minter: @{FlowEVMBridgeHandlerInterfaces.TokenMinter}
expectedMinterType: Type
) {
switch handlerType {
case Type<@CadenceNativeTokenHandler>():
let handler <-create CadenceNativeTokenHandler(
targetType: targetType,
targetEVMAddress: targetEVMAddress,
minter: <-minter
expectedMinterType: expectedMinterType
)
FlowEVMBridgeConfig.addTokenHandler(<-handler)
default:
Expand Down
34 changes: 28 additions & 6 deletions cadence/tests/flow_evm_bridge_handler_tests.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ access(all) let alice = Test.createAccount()

// ExampleToken
access(all) let exampleTokenIdentifier = "A.0000000000000011.ExampleHandledToken.Vault"
access(all) let exampleTokenMinterIdentifier = "A.0000000000000011.ExampleHandledToken.Minter"
access(all) let exampleTokenMintAmount = 100.0

// ERC20 values
Expand Down Expand Up @@ -285,18 +286,28 @@ fun testMintExampleTokenSucceeds() {
// Configuring the Handler also disables onboarding of the Cadence-native token to the bridge
access(all)
fun testConfigureCadenceNativeTokenHandlerSucceeds() {
let handlerSetupTxn = Test.Transaction(
code: Test.readFile("../transactions/bridge/admin/create_cadence_native_token_handler.cdc"),
authorizers: [exampleHandledTokenAccount.address, bridgeAccount.address],
signers: [exampleHandledTokenAccount, bridgeAccount],
arguments: [exampleTokenIdentifier, /storage/exampleTokenAdmin],
// Create TokenHandler for ExampleHandledToken, specifying the target type and expected minter type
let createHandlerResult = executeTransaction(
"../transactions/bridge/admin/create_cadence_native_token_handler.cdc",
[exampleTokenIdentifier, exampleTokenMinterIdentifier],
bridgeAccount
)
let createHandlerResult = Test.executeTransaction(handlerSetupTxn)
Test.expect(createHandlerResult, Test.beSucceeded())

// TODO: Add event validation when EVM and EVM dependent contracts can be imported to Test env
}

// Set the minter in the configured TokenHandler
access(all)
fun testSetTokenHandlerMinterSucceeds() {
let setHandlerMinterResult = executeTransaction(
"../transactions/bridge/admin/set_token_handler_minter.cdc",
[exampleTokenIdentifier, /storage/exampleTokenAdmin, bridgeAccount.address],
exampleHandledTokenAccount
)
Test.expect(setHandlerMinterResult, Test.beSucceeded())
}

// ExampleHandledToken no longer has minter after handoff, so minting should fail
access(all)
fun testMintExampleTokenFails() {
Expand All @@ -312,6 +323,17 @@ fun testMintExampleTokenFails() {
Test.expect(mintExampleTokenResult, Test.beFailed())
}

// Should not be able to enable TokenHandler without targetEVMAddress set
access(all)
fun testEnableTokenHandlerFails() {
let enabledResult = executeTransaction(
"../transactions/bridge/admin/enable_token_handler.cdc",
[exampleTokenIdentifier],
bridgeAccount
)
Test.expect(enabledResult, Test.beFailed())
}

// ERC20 deploys successfully - this will be used as the targetEVMAddress in our TokenHandler
access(all)
fun testDeployERC20Succeeds() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,33 @@ import "EVM"
import "FlowEVMBridgeHandlerInterfaces"
import "FlowEVMBridgeHandlers"

/// Creates a new TokenHandler for a Cadence-native fungible token, pulling a TokenMinter resource from the provided
/// storage path.
/// Creates a new TokenHandler for a Cadence-native fungible token and configures it in the bridge to handle the target
/// vault type. The minter enabling the handling of tokens as well as the target EVM address must also be set before
// the TokenHandler can be enabled.
///
/// @param vaultIdentifier: The identifier of the vault to create the TokenHandler for
/// @param minterStoragePath: The storage path to load the TokenMinter resource from
/// @param vaultIdentifier: The type identifier of the vault to create the TokenHandler for
/// @param minterIdentifier: The type identifier of the TokenMinter implementing resource
///
transaction(vaultIdentifier: String, minterStoragePath: StoragePath) {
transaction(vaultIdentifier: String, minterIdentifier: String) {

let configurator: auth(FlowEVMBridgeHandlerInterfaces.Admin) &FlowEVMBridgeHandlers.HandlerConfigurator
let minter: @{FlowEVMBridgeHandlerInterfaces.TokenMinter}

prepare(tokenMinter: auth(LoadValue) &Account, bridge: auth(BorrowValue, LoadValue) &Account) {
self.configurator = bridge.storage.borrow<auth(FlowEVMBridgeHandlerInterfaces.Admin) &FlowEVMBridgeHandlers.HandlerConfigurator>(
prepare(signer: auth(BorrowValue, LoadValue) &Account) {
self.configurator = signer.storage.borrow<auth(FlowEVMBridgeHandlerInterfaces.Admin) &FlowEVMBridgeHandlers.HandlerConfigurator>(
from: FlowEVMBridgeHandlers.ConfiguratorStoragePath
) ?? panic("Missing configurator")

self.minter <-tokenMinter.storage.load<@{FlowEVMBridgeHandlerInterfaces.TokenMinter}>(from: minterStoragePath)
?? panic("Minter not found at provided path")
}

execute {
let targetType = CompositeType(vaultIdentifier)
?? panic("Invalid vault identifier")
let minterType = CompositeType(minterIdentifier)
?? panic("Invalid minter identifier")
self.configurator.createTokenHandler(
handlerType: Type<@FlowEVMBridgeHandlers.CadenceNativeTokenHandler>(),
targetType: targetType,
targetEVMAddress: nil,
minter: <-self.minter
expectedMinterType: minterType
)
}
}
30 changes: 30 additions & 0 deletions cadence/transactions/bridge/admin/set_token_handler_minter.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import "EVM"

import "FlowEVMBridgeHandlerInterfaces"
import "FlowEVMBridgeHandlers"
import "FlowEVMBridgeConfig"

/// Sets the minter
///
/// @param vaultIdentifier: The type identifier of the vault to create the TokenHandler for
/// @param minterStoragePath: The type identifier of the TokenMinter implementing resource
///
transaction(vaultIdentifier: String, minterStoragePath: StoragePath, adminAddress: Address) {

let configAdmin: &FlowEVMBridgeConfig.Admin
let minter: @{FlowEVMBridgeHandlerInterfaces.TokenMinter}

prepare(signer: auth(LoadValue) &Account) {
self.configAdmin = getAccount(adminAddress).capabilities.borrow<&FlowEVMBridgeConfig.Admin>(
FlowEVMBridgeConfig.adminPublicPath
) ?? panic("Could not borrow reference to FlowEVMBridgeConfig.Admin")
self.minter <- signer.storage.load<@{FlowEVMBridgeHandlerInterfaces.TokenMinter}>(from: minterStoragePath)
?? panic("No minter found at provided storage path")
}

execute {
let targetType = CompositeType(vaultIdentifier)
?? panic("Invalid vault identifier")
self.configAdmin.setTokenHandlerMinter(targetType: targetType, minter: <-self.minter)
}
}

0 comments on commit 3f5d19e

Please sign in to comment.