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

UFix64 <-> UInt256 conversion fix and init test coverage #57

Merged
merged 12 commits into from
Apr 30, 2024
Merged
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
123 changes: 87 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,83 @@ 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

var e: UInt8 = 0
while fractional > 0 {
if fractional % 10 == 0 {
fractional = fractional / 10
e = e + 1
} else {
break
}
}

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")
)
assert(
fractional < UInt256(UFix64.max),
message: "Fractional ".concat(value.toString()).concat(" exceeds max UFix64 value")
)

return UFix64(scaledValue)
// Scale and add fractional part
let fractionalMultiplier = self.ufixPow(base: 0.1, exponent: decimals - e)
let scaledFractional: UFix64 = UFix64(fractional) * fractionalMultiplier
assert(scaledFractional < 1.0, message: "Scaled fractional exceeds 1.0")

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)
fun ufix64ToUInt256(value: UFix64, decimals: UInt8): UInt256 {
// 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 scale for integer and fractional parts
var integerMultiplier: UInt256 = self.pow(base:10, exponent: decimals)
let fractionalMultiplierExp: UInt8 = decimals < 8 ? decimals : decimals - 8
var fractionalMultiplier: UInt256 = self.pow(base:10, exponent: fractionalMultiplierExp)

return integer > 0 ? integer * integerMultiplier + UInt256(fractional) : fractionalMultiplier * UInt256(fractional)
}


/// Returns the value as a UInt64 if it fits, otherwise panics
///
access(all)
Expand Down Expand Up @@ -979,7 +1030,7 @@ contract FlowEVMBridgeUtils {
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 +1055,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 +1076,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 +1114,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 +1125,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 +1139,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 +1156,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 +1183,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 +1202,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 +1225,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
Loading