Skip to content

Commit

Permalink
Merge pull request #76 from onflow/standup-script
Browse files Browse the repository at this point in the history
Bridge standup script + Flow Port & Migrationnet prep
  • Loading branch information
sisyphusSmiling authored May 22, 2024
2 parents 8b8a624 + 02aab3f commit 6e5d9c2
Show file tree
Hide file tree
Showing 18 changed files with 4,629 additions and 93 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,7 @@ coverage.lcov
*.pem

# Local configs
local.flow.json
local.flow.json

# Local
.DS_Store
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ addresses:

|Contracts|PreviewNet|Testnet|Mainnet|
|---|---|---|---|
|All Cadence Bridge contracts|`0x715c57f7a59bc39b`|TBD|TBD|
|All Cadence Bridge contracts|`0x715c57f7a59bc39b`|`0xdfc20aee650fcbdf`|TBD|
|[`FlowEVMBridgeFactory.sol`](./solidity/src/FlowBridgeFactory.sol)|`0xf23c8619603434f7f71659820193c8e491feb1d9`|TBD|TBD|
|[`FlowEVMBridgeDeploymentRegistry.sol`](./solidity/src/FlowEVMBridgeDeploymentRegistry.sol)|`0x544ef4ed9209ebe6989bed9e543632512afb25de`|TBD|TBD|
|[`FlowEVMBridgedERC20Deployer.sol`](./solidity/src/FlowEVMBridgedERC20Deployer.sol)|`0xc5577d2935ef0556b37358d8b92aa578f1e7564e`|TBD|TBD|
Expand Down Expand Up @@ -87,8 +87,8 @@ that moving $FLOW to EVM is built into the `EVMAddress` object so any requests b
leverage this interface; however, moving $FLOW from EVM to Cadence must be done through the COA resource.

Below are transactions relevant to bridging fungible tokens:
- [`bridge_tokens_to_evm.cdc`](./cadence/transactions/bridge/ft/bridge_tokens_to_evm.cdc)
- [`bridge_tokens_from_evm.cdc`](./cadence/transactions/bridge/ft/bridge_tokens_from_evm.cdc)
- [`bridge_tokens_to_evm.cdc`](./cadence/transactions/bridge/tokens/bridge_tokens_to_evm.cdc)
- [`bridge_tokens_from_evm.cdc`](./cadence/transactions/bridge/tokens/bridge_tokens_from_evm.cdc)


## Prep Your Assets for Bridging
Expand Down
92 changes: 92 additions & 0 deletions cadence/args/bridged-nft-code-chunks-args-crescendo.json

Large diffs are not rendered by default.

65 changes: 65 additions & 0 deletions cadence/args/bridged-token-code-chunks-args-crescendo.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion cadence/contracts/bridge/FlowEVMBridgeUtils.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -1284,6 +1284,6 @@ contract FlowEVMBridgeUtils {
}
}
self.bridgeFactoryEVMAddress = EVMUtils.getEVMAddressFromHexString(address: bridgeFactoryAddressHex.toLower())
?? panic("Invalid EVM address hex")
?? panic("Invalid EVM address hex: ".concat(bridgeFactoryAddressHex))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ access(all) contract FlowEVMBridgeHandlerInterfaces {
///
access(all) resource interface HandlerInfo {
/// Returns whether the Handler is enabled
access(all) view fun isEnabled(): Bool
access(all) view fun isEnabled(): Bool
/// Returns the Cadence type handled by the Handler, nil if not set
access(all) view fun getTargetType(): Type?
/// Returns the EVM address handled by the Handler, nil if not set
Expand Down
31 changes: 7 additions & 24 deletions cadence/contracts/utils/EVMUtils.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,7 @@ access(all) contract EVMUtils {
// TODO: Remove once EVMAddress.toString() is available
access(all)
view fun getEVMAddressAsHexString(address: EVM.EVMAddress): String {
let bytes = address.bytes
// Iterating & appending to an array is not allowed in a `view` method and this method must be `view` for
// certain use cases in the bridge contracts - namely for emitting values in pre- & post-conditions
let addressBytes: [UInt8] = [
bytes[0], bytes[1], bytes[2], bytes[3],
bytes[4], bytes[5], bytes[6], bytes[7],
bytes[8], bytes[9], bytes[10], bytes[11],
bytes[12], bytes[13], bytes[14], bytes[15],
bytes[16], bytes[17], bytes[18], bytes[19]
]
return String.encodeHex(addressBytes)
return String.encodeHex(address.bytes.toVariableSized())
}

/// Returns an EVMAddress as a hex string without a 0x prefix, truncating the string's last 20 bytes if exceeded
Expand All @@ -33,19 +23,12 @@ access(all) contract EVMUtils {
///
access(all)
fun getEVMAddressFromHexString(address: String): EVM.EVMAddress? {
if address.length != 40 {
return nil
pre {
address.length == 40 || address.length == 42: "Invalid hex string length"
}
var addressBytes: [UInt8] = address.toLower().decodeHex()
if addressBytes.length != 20 {
return nil
}
return EVM.EVMAddress(bytes: [
addressBytes[0], addressBytes[1], addressBytes[2], addressBytes[3],
addressBytes[4], addressBytes[5], addressBytes[6], addressBytes[7],
addressBytes[8], addressBytes[9], addressBytes[10], addressBytes[11],
addressBytes[12], addressBytes[13], addressBytes[14], addressBytes[15],
addressBytes[16], addressBytes[17], addressBytes[18], addressBytes[19]
])
// Strip the 0x prefix if it exists
var withoutPrefix = (address[1] == "x" ? address.slice(from: 2, upTo: address.length) : address).toLower()
let bytes = withoutPrefix.decodeHex().toConstantSized<[UInt8;20]>()!
return EVM.EVMAddress(bytes: bytes)
}
}
17 changes: 17 additions & 0 deletions cadence/scripts/bridge/get_associated_type.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import "EVM"

import "EVMUtils"
import "FlowEVMBridgeConfig"

/// Returns the Cadence Type associated with the given EVM address (as its hex String)
///
/// @param evmAddressHex: The hex-encoded address of the EVM contract as a String
///
/// @return The Cadence Type associated with the EVM address or nil if the address is not onboarded. `nil` may also be
/// returned if the address is not a valid EVM address.
///
access(all)
fun main(addressHex: String): Type? {
let address = EVMUtils.getEVMAddressFromHexString(address: addressHex)
return address != nil ? FlowEVMBridgeConfig.getTypeAssociated(with: address!) : nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import "EVMUtils"
import "FlowEVMBridge"
import "FlowEVMBridgeConfig"

/// This transaction onboards the NFT type to the bridge, configuring the bridge to move NFTs between environments
/// This transaction onboards ERC20/ERC721 assets to the bridge, configuring the bridge to move assets between
/// environments
/// NOTE: This must be done before bridging a Cadence-native NFT to EVM
///
/// @param addressesAsHex: Array of EVM contract addresses (as hex string without 0x prefix) defining the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import "EVM"
import "FlowEVMBridge"
import "FlowEVMBridgeConfig"

/// This transaction onboards the asset type to the bridge, configuring the bridge to move assets between environments
/// This transaction onboards ERC20/ERC721 assets to the bridge, configuring the bridge to move assets between
/// environments
/// NOTE: This must be done before bridging a Cadence-native asset to EVM
///
/// @param types: The Cadence types of the bridgeable asset to onboard to the bridge
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import "EVM"

import "EVMUtils"
import "FlowEVMBridgeUtils"

/// Executes an NFT transfer to the defined recipient address against the specified ERC721 contract.
///
transaction(evmContractAddressHex: String, recipientAddressHex: String, id: UInt256) {

let evmContractAddress: EVM.EVMAddress
let recipientAddress: EVM.EVMAddress
let coa: auth(EVM.Call) &EVM.CadenceOwnedAccount
var senderOwnerCheck: Bool
var recipientOwnerCheck: Bool

prepare(signer: auth(BorrowValue) &Account) {
self.evmContractAddress = EVMUtils.getEVMAddressFromHexString(address: evmContractAddressHex) ?? panic("Invalid contract address")
self.recipientAddress = EVMUtils.getEVMAddressFromHexString(address: recipientAddressHex) ?? panic("Invalid recipient address")

self.coa = signer.storage.borrow<auth(EVM.Call) &EVM.CadenceOwnedAccount>(from: /storage/evm)
?? panic("Could not borrow CadenceOwnedAccount reference")

self.senderOwnerCheck = FlowEVMBridgeUtils.isOwnerOrApproved(
ofNFT: id,
owner: self.coa.address(),
evmContractAddress: self.evmContractAddress
)
self.recipientOwnerCheck = false
}

execute {
let calldata = EVM.encodeABIWithSignature(
"safeTransferFrom(address,address,uint256)",
[self.coa.address(), self.recipientAddress, id]
)
let callResult = self.coa.call(
to: self.evmContractAddress,
data: calldata,
gasLimit: 15_000_000,
value: EVM.Balance(attoflow: 0)
)
assert(callResult.status == EVM.Status.successful, message: "Call to ERC721 contract failed")
self.recipientOwnerCheck = FlowEVMBridgeUtils.isOwnerOrApproved(
ofNFT: id,
owner: self.recipientAddress,
evmContractAddress: self.evmContractAddress
)
}

post {
self.recipientOwnerCheck: "Recipient did not receive the token"
}
}
42 changes: 42 additions & 0 deletions cadence/transactions/example-assets/evm-assets/transfer_erc20.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import "EVM"

import "EVMUtils"
import "FlowEVMBridgeUtils"

/// Executes a token transfer to the defined recipient address against the specified ERC20 contract.
///
transaction(evmContractAddressHex: String, recipientAddressHex: String, amount: UInt256) {

let evmContractAddress: EVM.EVMAddress
let recipientAddress: EVM.EVMAddress
let coa: auth(EVM.Call) &EVM.CadenceOwnedAccount
let preBalance: UInt256
var postBalance: UInt256

prepare(signer: auth(BorrowValue) &Account) {
self.evmContractAddress = EVMUtils.getEVMAddressFromHexString(address: evmContractAddressHex) ?? panic("Invalid contract address")
self.recipientAddress = EVMUtils.getEVMAddressFromHexString(address: recipientAddressHex) ?? panic("Invalid recipient address")

self.coa = signer.storage.borrow<auth(EVM.Call) &EVM.CadenceOwnedAccount>(from: /storage/evm)
?? panic("Could not borrow CadenceOwnedAccount reference")

self.preBalance = FlowEVMBridgeUtils.balanceOf(owner: self.coa.address(), evmContractAddress: self.evmContractAddress)
self.postBalance = 0
}

execute {
let calldata = EVM.encodeABIWithSignature("transfer(address,uint256)", [self.recipientAddress, amount])
let callResult = self.coa.call(
to: self.evmContractAddress,
data: calldata,
gasLimit: 15_000_000,
value: EVM.Balance(attoflow: 0)
)
assert(callResult.status == EVM.Status.successful, message: "Call to ERC20 contract failed")
self.postBalance = FlowEVMBridgeUtils.balanceOf(owner: self.coa.address(), evmContractAddress: self.evmContractAddress)
}

post {
self.postBalance == self.preBalance - amount: "Transfer failed"
}
}
106 changes: 106 additions & 0 deletions cadence/transactions/flow-token/dynamic_vm_transfer.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import "FungibleToken"
import "FlowToken"

import "EVM"

import "EVMUtils"

// Transfers $FLOW from the signer's account to the recipient's address, determining the target VM based on the format
// of the recipient's hex address. Note that the sender's funds are sourced by default from the target VM, pulling any
// difference from the alternate VM if available. e.g. Transfers to Flow addresses will first attempt to withdraw from
// the signer's Flow vault, pulling any remaining funds from the signer's EVM account if available. Transfers to EVM
// addresses will first attempt to withdraw from the signer's EVM account, pulling any remaining funds from the signer's
// Flow vault if available. If the signer's balance across both VMs is insufficient, the transaction will revert.
///
/// @param addressString: The recipient's address in hex format - this should be either an EVM address or a Flow address
/// @param amount: The amount of $FLOW to transfer as a UFix64 value
///
transaction(addressString: String, amount: UFix64) {

let sentVault: @FlowToken.Vault
let evmRecipient: EVM.EVMAddress?
var receiver: &{FungibleToken.Receiver}?

prepare(signer: auth(BorrowValue, SaveValue) &Account) {
// Reference signer's COA if one exists
let coa = signer.storage.borrow<auth(EVM.Withdraw) &EVM.CadenceOwnedAccount>(from: /storage/evm)

// Reference signer's FlowToken Vault
let sourceVault = signer.storage.borrow<auth(FungibleToken.Withdraw) &FlowToken.Vault>(from: /storage/flowTokenVault)
?? panic("Could not borrow signer's FlowToken.Vault")
let cadenceBalance = sourceVault.balance

// Define optional recipients for both VMs
self.receiver = nil
let cadenceRecipient = Address.fromString(addressString)
self.evmRecipient = cadenceRecipient == nil ? EVMUtils.getEVMAddressFromHexString(address: addressString) : nil
// Validate exactly one target address is assigned
if cadenceRecipient != nil && self.evmRecipient != nil {
panic("Malformed recipient address - assignable as both Cadence and EVM addresses")
} else if cadenceRecipient == nil && self.evmRecipient == nil {
panic("Malformed recipient address - not assignable as either Cadence or EVM address")
}

// Create empty FLOW vault to capture funds
self.sentVault <- FlowToken.createEmptyVault(vaultType: Type<@FlowToken.Vault>())
/// If the target VM is Flow, does the Vault have sufficient balance to cover?
if cadenceRecipient != nil {
// Assign the Receiver of the $FLOW transfer
self.receiver = getAccount(cadenceRecipient!).capabilities.borrow<&{FungibleToken.Receiver}>(
/public/flowTokenReceiver
) ?? panic("Could not borrow reference to recipient's FungibleToken.Receiver")

// Withdraw from the signer's Cadence Vault and deposit to sentVault
var withdrawAmount = amount < cadenceBalance ? amount : cadenceBalance
self.sentVault.deposit(from: <-sourceVault.withdraw(amount: withdrawAmount))

// If the cadence balance didn't cover the amount, check the signer's EVM balance
if amount > self.sentVault.balance {
let difference = amount - cadenceBalance
// Revert if the signer doesn't have an EVM account or EVM balance is insufficient
if coa == nil || difference < coa!.balance().inFLOW() {
panic("Insufficient balance across Flow and EVM accounts")
}

// Withdraw from the signer's EVM account and deposit to sentVault
let withdrawFromEVM = EVM.Balance(attoflow: 0)
withdrawFromEVM.setFLOW(flow: difference)
self.sentVault.deposit(from: <-coa!.withdraw(balance: withdrawFromEVM))
}
} else if self.evmRecipient != nil {
// Check signer's balance can cover the amount
if coa != nil {
// Determine the amount to withdraw from the signer's EVM account
let balance = coa!.balance()
let withdrawAmount = amount < balance.inFLOW() ? amount : balance.inFLOW()
balance.setFLOW(flow: withdrawAmount)

// Withdraw funds from EVM to the sentVault
self.sentVault.deposit(from: <-coa!.withdraw(balance: balance))
}
if amount > self.sentVault.balance {
// Insufficient amount withdrawn from EVM, check signer's Flow balance
let difference = amount - self.sentVault.balance
if difference > cadenceBalance {
panic("Insufficient balance across Flow and EVM accounts")
}
// Withdraw from the signer's Cadence Vault and deposit to sentVault
self.sentVault.deposit(from: <-sourceVault.withdraw(amount: difference))
}
}
}

pre {
self.sentVault.balance == amount: "Attempting to send an incorrect amount of $FLOW"
}

execute {
// Complete Cadence transfer if the FungibleToken Receiver is assigned
if self.receiver != nil {
self.receiver!.deposit(from: <-self.sentVault)
} else {
// Otherwise, complete EVM transfer
self.evmRecipient!.deposit(from: <-self.sentVault)
}
}
}
Loading

0 comments on commit 6e5d9c2

Please sign in to comment.