Skip to content

Commit

Permalink
Merge branch 'main' into update-coa-deploy
Browse files Browse the repository at this point in the history
  • Loading branch information
sisyphusSmiling committed May 1, 2024
2 parents 98d64d5 + eda2f00 commit 0d34b1d
Show file tree
Hide file tree
Showing 11 changed files with 555 additions and 226 deletions.
2 changes: 0 additions & 2 deletions .github/workflows/cadence_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ jobs:
run: echo "/root/.local/bin" >> $GITHUB_PATH
- name: Run tests
run: sh local/run_cadence_tests.sh
- name: Normalize coverage report filepaths
run: sh ./local/normalize_coverage_report.sh
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v3
env:
Expand Down
3 changes: 3 additions & 0 deletions cadence/contracts/bridge/FlowEVMBridge.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,8 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge {
vaultBalance,
erc20Address: associatedAddress
)
assert(bridgeAmount > UInt256(0), message: "Amount to bridge must be greater than 0")

// Determine if the EVM contract is bridge-owned - affects how tokens are transmitted to recipient
let isFactoryDeployed = FlowEVMBridgeUtils.isEVMContractBridgeOwned(evmContractAddress: associatedAddress)

Expand Down Expand Up @@ -469,6 +471,7 @@ contract FlowEVMBridge : IFlowEVMNFTBridge, IFlowEVMTokenBridge {
let definingContractName = FlowEVMBridgeUtils.getContractName(fromType: type)!
// Convert the amount to a ufix64 so the amount can be settled on the Cadence side
let ufixAmount = FlowEVMBridgeUtils.convertERC20AmountToCadenceAmount(amount, erc20Address: associatedAddress)
assert(ufixAmount > 0.0, message: "Amount to bridge must be greater than 0")

/* Execute the transfer call and make needed state assertions */
//
Expand Down
3 changes: 3 additions & 0 deletions cadence/contracts/bridge/FlowEVMBridgeHandlers.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ access(all) contract FlowEVMBridgeHandlers {
let amount = tokens.balance
let uintAmount = FlowEVMBridgeUtils.convertCadenceAmountToERC20Amount(amount, erc20Address: evmAddress)

assert(uintAmount > UInt256(0), message: "Amount to bridge must be greater than 0")

Burner.burn(<-tokens)

FlowEVMBridgeUtils.mustTransferERC20(to: to, amount: uintAmount, erc20Address: evmAddress)
Expand Down Expand Up @@ -134,6 +136,7 @@ access(all) contract FlowEVMBridgeHandlers {
amount,
erc20Address: evmAddress
)
assert(ufixAmount > 0.0, message: "Amount to bridge must be greater than 0")

FlowEVMBridgeUtils.mustEscrowERC20(
owner: owner,
Expand Down
140 changes: 104 additions & 36 deletions cadence/contracts/bridge/FlowEVMBridgeUtils.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,8 @@ contract FlowEVMBridgeUtils {
///
access(all)
fun isValidEVMAsset(evmContractAddress: EVM.EVMAddress): Bool {
let isERC721 = FlowEVMBridgeUtils.isERC721(evmContractAddress: evmContractAddress)
let isERC20 = FlowEVMBridgeUtils.isERC20(evmContractAddress: evmContractAddress)
let isERC721 = self.isERC721(evmContractAddress: evmContractAddress)
let isERC20 = self.isERC20(evmContractAddress: evmContractAddress)
return (isERC721 && !isERC20) || (!isERC721 && isERC20)
}

Expand All @@ -280,7 +280,7 @@ contract FlowEVMBridgeUtils {
///
access(all)
view fun getBridgeCOAEVMAddress(): EVM.EVMAddress {
return FlowEVMBridgeUtils.borrowCOA().address()
return self.borrowCOA().address()
}

/// Retrieves the relevant information for onboarding a Cadence asset to the bridge. This method is used to
Expand All @@ -303,10 +303,10 @@ contract FlowEVMBridgeUtils {
let isNFT = forAssetType.isSubtype(of: Type<@{NonFungibleToken.NFT}>())

// Retrieve the Cadence type's defining contract name, address, & its identifier
var name = FlowEVMBridgeUtils.getContractName(fromType: forAssetType)
var name = self.getContractName(fromType: forAssetType)
?? panic("Could not contract name from type: ".concat(forAssetType.identifier))
let identifier = forAssetType.identifier
let cadenceAddress = FlowEVMBridgeUtils.getContractAddress(fromType: forAssetType)
let cadenceAddress = self.getContractAddress(fromType: forAssetType)
?? panic("Could not derive contract address for token type: ".concat(identifier))
// Initialize asset symbol which will be assigned later
// based on presence of asset-defined metadata
Expand Down Expand Up @@ -607,7 +607,7 @@ contract FlowEVMBridgeUtils {
fun erc721Exists(erc721Address: EVM.EVMAddress, id: UInt256): Bool {
let existsResponse = EVM.decodeABI(
types: [Type<Bool>()],
data: FlowEVMBridgeUtils.call(
data: self.call(
signature: "exists(uint256)",
targetEVMAddress: erc721Address,
args: [id],
Expand Down Expand Up @@ -793,32 +793,100 @@ contract FlowEVMBridgeUtils {
return r
}

/// Raises the fixed point base to the power of the exponent
///
access(all)
view fun ufixPow(base: UFix64, exponent: UInt8): UFix64 {
if exponent == 0 {
return 1.0
}

var r = base
var exp: UInt8 = 1
while exp < exponent {
r = r * base
exp = exp + 1
}

return r
}

/// Converts a UInt256 to a UFix64
///
access(all)
view fun uint256ToUFix64(value: UInt256, decimals: UInt8): UFix64 {
let scaleFactor: UInt256 = self.pow(base: 10, exponent: decimals)
let scaledValue: UInt256 = value / scaleFactor
// Calculate scale factors for the integer and fractional parts
let absoluteScaleFactor = self.pow(base: 10, exponent: decimals)

// Separate the integer and fractional parts of the value
let scaledValue = value / absoluteScaleFactor
var fractional = value % absoluteScaleFactor
let scaledFractional = self.uint256FractionalToScaledUFix64Decimals(value: fractional, decimals: decimals)

assert(
scaledValue < UInt256(UInt64.max),
message: "Value ".concat(value.toString()).concat(" exceeds max UFix64 value")
scaledValue < UInt256(UFix64.max),
message: "Scaled integer value ".concat(value.toString()).concat(" exceeds max UFix64 value")
)

return UFix64(scaledValue)
return UFix64(scaledValue) + scaledFractional
}

/// Converts a UFix64 to a UInt256
//
access(all)
view fun ufix64ToUInt256(value: UFix64, decimals: UInt8): UInt256 {
let integerPart: UInt64 = UInt64(value)
var r = UInt256(integerPart)
// Default to 10e8 scale, catching instances where decimals are less than default and scale appropriately
let ufixScaleExp: UInt8 = decimals < 8 ? decimals : 8
var ufixScale = self.ufixPow(base: 10.0, exponent: ufixScaleExp)

// Separate the fractional and integer parts of the UFix64
let integer = UInt256(value)
var fractional = (value % 1.0) * ufixScale

var multiplier: UInt256 = self.pow(base:10, exponent: decimals)
return r * multiplier
// Calculate the multiplier for integer and fractional parts
var integerMultiplier: UInt256 = self.pow(base:10, exponent: decimals)
let fractionalMultiplierExp: UInt8 = decimals < 8 ? 0 : decimals - 8
var fractionalMultiplier: UInt256 = self.pow(base:10, exponent: fractionalMultiplierExp)

// Scale and sum the parts
return integer * integerMultiplier + UInt256(fractional) * fractionalMultiplier
}

/// Converts a UInt256 fractional value with the given decimal places to a scaled UFix64. Note that UFix64 has
/// decimal precision of 8 places so converted values may lose precision and be rounded down.
///
access(all)
view fun uint256FractionalToScaledUFix64Decimals(value: UInt256, decimals: UInt8): UFix64 {
post {
result < 1.0: "Scaled fractional exceeds 1.0"
}
var fractional = value
// Reduce fractional values with trailing zeros
var e: UInt8 = 0
while fractional > 0 {
if fractional % 10 == 0 {
fractional = fractional / 10
e = e + 1
} else {
break
}
}

// fractional is too long - since UFix64 has 8 decimal places, truncate to maintain only the first 8 digis
var fractionalReduction: UInt8 = 0
while fractional > 99999999 {
fractional = fractional / 10
fractionalReduction = fractionalReduction + 1
}

// Scale the fractional part
let fractionalMultiplier = self.ufixPow(base: 0.1, exponent: decimals - e - fractionalReduction)
let scaledFractional = UFix64(fractional) * fractionalMultiplier

return scaledFractional
}


/// Returns the value as a UInt64 if it fits, otherwise panics
///
access(all)
Expand Down Expand Up @@ -978,8 +1046,8 @@ contract FlowEVMBridgeUtils {
let toPreStatus = self.isOwner(ofNFT: id, owner: to, evmContractAddress: erc721Address)
assert(bridgePreStatus, message: "Bridge COA does not own ERC721 requesting to be transferred")
assert(!toPreStatus, message: "Recipient already owns ERC721 attempting to be transferred")
let transferResult: EVM.Result = FlowEVMBridgeUtils.call(

let transferResult: EVM.Result = self.call(
signature: "safeTransferFrom(address,address,uint256)",
targetEVMAddress: erc721Address,
args: [bridgeCOAAddress, to, id],
Expand All @@ -1004,7 +1072,7 @@ contract FlowEVMBridgeUtils {
fun mustSafeMintERC721(erc721Address: EVM.EVMAddress, to: EVM.EVMAddress, id: UInt256, uri: String) {
let bridgeCOAAddress = self.getBridgeCOAEVMAddress()

let mintResult: EVM.Result = FlowEVMBridgeUtils.call(
let mintResult: EVM.Result = self.call(
signature: "safeMint(address,uint256,string)",
targetEVMAddress: erc721Address,
args: [to, id, uri],
Expand All @@ -1025,7 +1093,7 @@ contract FlowEVMBridgeUtils {
fun mustUpdateTokenURI(erc721Address: EVM.EVMAddress, id: UInt256, uri: String) {
let bridgeCOAAddress = self.getBridgeCOAEVMAddress()

let updateResult: EVM.Result = FlowEVMBridgeUtils.call(
let updateResult: EVM.Result = self.call(
signature: "updateTokenURI(uint256,string)",
targetEVMAddress: erc721Address,
args: [id, uri],
Expand Down Expand Up @@ -1063,9 +1131,9 @@ contract FlowEVMBridgeUtils {
///
access(account)
fun mustMintERC20(to: EVM.EVMAddress, amount: UInt256, erc20Address: EVM.EVMAddress) {
let toPreBalance = FlowEVMBridgeUtils.balanceOf(owner: to, evmContractAddress: erc20Address)
let toPreBalance = self.balanceOf(owner: to, evmContractAddress: erc20Address)
// Mint tokens to the recipient
let mintResult: EVM.Result = FlowEVMBridgeUtils.call(
let mintResult: EVM.Result = self.call(
signature: "mint(address,uint256)",
targetEVMAddress: erc20Address,
args: [to, amount],
Expand All @@ -1074,7 +1142,7 @@ contract FlowEVMBridgeUtils {
)
assert(mintResult.status == EVM.Status.successful, message: "Mint to bridge ERC20 contract failed")
// Ensure bridge to recipient was succcessful
let toPostBalance = FlowEVMBridgeUtils.balanceOf(owner: to, evmContractAddress: erc20Address)
let toPostBalance = self.balanceOf(owner: to, evmContractAddress: erc20Address)
assert(
toPostBalance == toPreBalance + amount,
message: "Recipient didn't receive minted ERC20 tokens during bridging"
Expand All @@ -1088,14 +1156,14 @@ contract FlowEVMBridgeUtils {
fun mustTransferERC20(to: EVM.EVMAddress, amount: UInt256, erc20Address: EVM.EVMAddress) {
let bridgeCOAAddress = self.getBridgeCOAEVMAddress()

let toPreBalance = FlowEVMBridgeUtils.balanceOf(owner: to, evmContractAddress: erc20Address)
let escrowPreBalance = FlowEVMBridgeUtils.balanceOf(
let toPreBalance = self.balanceOf(owner: to, evmContractAddress: erc20Address)
let escrowPreBalance = self.balanceOf(
owner: bridgeCOAAddress,
evmContractAddress: erc20Address
)

// Transfer tokens to the recipient
let transferResult: EVM.Result = FlowEVMBridgeUtils.call(
let transferResult: EVM.Result = self.call(
signature: "transfer(address,uint256)",
targetEVMAddress: erc20Address,
args: [to, amount],
Expand All @@ -1105,8 +1173,8 @@ contract FlowEVMBridgeUtils {
assert(transferResult.status == EVM.Status.successful, message: "transfer call to ERC20 contract failed")

// Ensure bridge to recipient was succcessful
let toPostBalance = FlowEVMBridgeUtils.balanceOf(owner: to, evmContractAddress: erc20Address)
let escrowPostBalance = FlowEVMBridgeUtils.balanceOf(
let toPostBalance = self.balanceOf(owner: to, evmContractAddress: erc20Address)
let escrowPostBalance = self.balanceOf(
owner: bridgeCOAAddress,
evmContractAddress: erc20Address
)
Expand All @@ -1132,17 +1200,17 @@ contract FlowEVMBridgeUtils {
protectedTransferCall: fun (): EVM.Result
) {
// Ensure the caller is has sufficient balance to bridge the requested amount
let hasSufficientBalance = FlowEVMBridgeUtils.hasSufficientBalance(
let hasSufficientBalance = self.hasSufficientBalance(
amount: amount,
owner: owner,
evmContractAddress: erc20Address
)
assert(hasSufficientBalance, message: "Caller does not have sufficient balance to bridge requested tokens")

// Get the owner and escrow balances before transfer
let ownerPreBalance = FlowEVMBridgeUtils.balanceOf(owner: owner, evmContractAddress: erc20Address)
let bridgePreBalance = FlowEVMBridgeUtils.balanceOf(
owner: FlowEVMBridgeUtils.getBridgeCOAEVMAddress(),
let ownerPreBalance = self.balanceOf(owner: owner, evmContractAddress: erc20Address)
let bridgePreBalance = self.balanceOf(
owner: self.getBridgeCOAEVMAddress(),
evmContractAddress: erc20Address
)

Expand All @@ -1151,9 +1219,9 @@ contract FlowEVMBridgeUtils {
assert(transferResult.status == EVM.Status.successful, message: "Transfer via callback failed")

// Get the resulting balances after transfer
let ownerPostBalance = FlowEVMBridgeUtils.balanceOf(owner: owner, evmContractAddress: erc20Address)
let bridgePostBalance = FlowEVMBridgeUtils.balanceOf(
owner: FlowEVMBridgeUtils.getBridgeCOAEVMAddress(),
let ownerPostBalance = self.balanceOf(owner: owner, evmContractAddress: erc20Address)
let bridgePostBalance = self.balanceOf(
owner: self.getBridgeCOAEVMAddress(),
evmContractAddress: erc20Address
)

Expand All @@ -1174,9 +1242,9 @@ contract FlowEVMBridgeUtils {
isERC721: Bool
): EVM.EVMAddress {
let signature = isERC721 ? "deployERC721(string,string,string,string,string)" : "deployERC20(string,string,string,string,string)"
let deployResult: EVM.Result = FlowEVMBridgeUtils.call(
let deployResult: EVM.Result = self.call(
signature: signature,
targetEVMAddress: FlowEVMBridgeUtils.bridgeFactoryEVMAddress,
targetEVMAddress: self.bridgeFactoryEVMAddress,
args: [name, symbol, cadenceAddress.toString(), flowIdentifier, contractURI],
gasLimit: 15000000,
value: 0.0
Expand Down
Loading

0 comments on commit 0d34b1d

Please sign in to comment.