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

Bridge standup script + Flow Port & Migrationnet prep #76

Merged
merged 20 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
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
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)
}
}
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"
}
}
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
Loading