Skip to content

Commit

Permalink
Merge pull request #62 from onflow/fix-unit-conversion
Browse files Browse the repository at this point in the history
Fix UInt256 <-> UFix64 conversion & add test coverage
  • Loading branch information
sisyphusSmiling authored May 1, 2024
2 parents d98a19e + 6988021 commit eda2f00
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 29 deletions.
65 changes: 41 additions & 24 deletions cadence/contracts/bridge/FlowEVMBridgeUtils.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -821,38 +821,20 @@ contract FlowEVMBridgeUtils {
// 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
}
}
let scaledFractional = self.uint256FractionalToScaledUFix64Decimals(value: fractional, decimals: decimals)

assert(
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")
)

// 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)
fun ufix64ToUInt256(value: UFix64, decimals: UInt8): UInt256 {
view 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)
Expand All @@ -861,12 +843,47 @@ contract FlowEVMBridgeUtils {
let integer = UInt256(value)
var fractional = (value % 1.0) * ufixScale

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

return integer > 0 ? integer * integerMultiplier + UInt256(fractional) : fractionalMultiplier * UInt256(fractional)
// 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
}


Expand Down Expand Up @@ -1029,7 +1046,7 @@ 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 = self.call(
signature: "safeTransferFrom(address,address,uint256)",
targetEVMAddress: erc721Address,
Expand Down
52 changes: 47 additions & 5 deletions cadence/tests/flow_evm_bridge_utils_tests.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ fun testReducedPrecisionUInt256ToUFix64Succeeds() {
let ufixAmount: UFix64 = 24_244_814.05459100

let actualUFixAmount = uint256ToUFix64(uintAmount, decimals: 6)
Test.assert(actualUFixAmount == ufixAmount)
Test.assertEqual(ufixAmount, actualUFixAmount)
}

// Converting from UFix64 to UInt256 with reduced point precision (6 vs. 8) should round down
Expand All @@ -125,7 +125,7 @@ fun testReducedPrecisionUFix64ToUInt256Succeeds() {
let ufixAmount: UFix64 = 24_244_814.05459154

let actualUIntAmount = ufix64ToUInt256(ufixAmount, decimals: 6)
Test.assert(actualUIntAmount == uintAmount)
Test.assertEqual(uintAmount, actualUIntAmount)
}

access(all)
Expand All @@ -134,8 +134,8 @@ fun testDustUInt256ToUFix64Succeeds() {
let dustUIntAmount: UInt256 = 25_470_000_000_000

let actualUFixAmount = uint256ToUFix64(dustUIntAmount, decimals: 18)
assert(actualUFixAmount <= dustUFixAmount, message: "Actual UFix amount greater: ".concat(actualUFixAmount.toString()))
assert(actualUFixAmount > 0.0, message: "Actual UFix zero: ".concat(actualUFixAmount.toString()))
Test.assertEqual(dustUFixAmount, actualUFixAmount)
Test.assert(actualUFixAmount > 0.0)
}

access(all)
Expand All @@ -144,7 +144,8 @@ fun testDustUFix64ToUInt256Succeeds() {
let dustUIntAmount: UInt256 = 25_470_000_000_000

let actualUIntAmount = ufix64ToUInt256(dustUFixAmount, decimals: 18)
Test.assert(actualUIntAmount == dustUIntAmount && actualUIntAmount > 0)
Test.assertEqual(dustUIntAmount, actualUIntAmount)
Test.assert(actualUIntAmount > 0)
}

access(all)
Expand All @@ -164,3 +165,44 @@ fun testZeroUFix64ToUInt256Succeeds() {
let actualUIntAmount = ufix64ToUInt256(zeroUFixAmount, decimals: 18)
Test.assertEqual(zeroUIntAmount, actualUIntAmount)
}

access(all)
fun testNonFractionalUInt256ToUFix64Succeeds() {
let nonFractionalUFixAmount: UFix64 = 100.0
let nonFractionalUIntAmount: UInt256 = 100_000_000_000_000_000_000

let actualUFixAmount = uint256ToUFix64(nonFractionalUIntAmount, decimals: 18)
Test.assertEqual(nonFractionalUFixAmount, actualUFixAmount)
}

access(all)
fun testNonFractionalUFix64ToUInt256Succeeds() {
let nonFractionalUFixAmount: UFix64 = 100.0
let nonFractionalUIntAmount: UInt256 = 100_000_000_000_000_000_000

let actualUIntAmount = ufix64ToUInt256(nonFractionalUFixAmount, decimals: 18)
Test.assertEqual(nonFractionalUIntAmount, actualUIntAmount)
}

access(all)
fun testLargeFractionalUInt256ToUFix64Succeeds() {
let largeFractionalUFixAmount: UFix64 = 1.99785982
let largeFractionalUIntAmount: UInt256 = 1_997_859_829_999_999_999

log("testLargeFractionalUInt256ToUFix64Succeeds")

let actualUFixAmount = uint256ToUFix64(largeFractionalUIntAmount, decimals: 18)
Test.assertEqual(largeFractionalUFixAmount, actualUFixAmount)
}

access(all)
fun testlargeFractionalUFix64ToUInt256Succeeds() {
let largeFractionalUFixAmount: UFix64 = 1.99785982
let largeFractionalUIntAmount: UInt256 = 1_997_859_820_000_000_000
// let largeFractionalUIntAmount: UInt256 = 1,997,859,820,000,000,000
log("testlargeFractionalUFix64ToUInt256Succeeds")

let actualUIntAmount = ufix64ToUInt256(largeFractionalUFixAmount, decimals: 18)
Test.assertEqual(largeFractionalUIntAmount, actualUIntAmount)
}

0 comments on commit eda2f00

Please sign in to comment.