Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
11 changes: 10 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,14 @@
test:
flow test --cover --covercode="contracts" --coverprofile="coverage.lcov" ./cadence/tests/*_test.cdc

.PHONY: lint
lint:
@output=$$(flow cadence lint $$(find cadence -name "*.cdc" -not -path "*/tests/transactions/attempt_copy_auto_balancer_config.cdc") 2>&1); \
echo "$$output"; \
if echo "$$output" | grep -qE "[1-9][0-9]* problems"; then \
echo "Lint failed: problems found"; \
exit 1; \
fi

.PHONY: ci
ci: test
ci: lint test
6 changes: 2 additions & 4 deletions cadence/contracts/connectors/SwapConnectors.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -235,8 +235,7 @@ access(all) contract SwapConnectors {
init(swapper: {DeFiActions.Swapper}, sink: {DeFiActions.Sink}, uniqueID: DeFiActions.UniqueIdentifier?) {
pre {
swapper.outType() == sink.getSinkType():
"Swapper outputs \(swapper.outType().identifier) but Sink takes \(sink.getSinkType().identifier) - "
.concat("Ensure the provided Swapper outputs a Vault Type compatible with the provided Sink")
"Swapper outputs \(swapper.outType().identifier) but Sink takes \(sink.getSinkType().identifier) - Ensure the provided Swapper outputs a Vault Type compatible with the provided Sink"
}
self.swapper = swapper
self.sink = sink
Expand Down Expand Up @@ -539,8 +538,7 @@ access(all) contract SwapConnectors {
init(swapper: {DeFiActions.Swapper}, source: {DeFiActions.Source}, uniqueID: DeFiActions.UniqueIdentifier?) {
pre {
source.getSourceType() == swapper.inType():
"Source outputs \(source.getSourceType().identifier) but Swapper takes \(swapper.inType().identifier) - "
.concat("Ensure the provided Source outputs a Vault Type compatible with the provided Swapper")
"Source outputs \(source.getSourceType().identifier) but Swapper takes \(swapper.inType().identifier) - Ensure the provided Source outputs a Vault Type compatible with the provided Swapper"
}
self.swapper = swapper
self.source = source
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,11 @@ access(all) contract BandOracleConnectors {

// check price data has not gone stale based on last updated timestamp
let now = UInt64(getCurrentBlock().timestamp)
if self.staleThreshold != nil {
assert(now < priceData.baseTimestamp + self.staleThreshold!,
message: "Price data's base timestamp \(priceData.baseTimestamp) exceeds the staleThreshold "
.concat("\(priceData.baseTimestamp + self.staleThreshold!) at current timestamp \(now)"))
assert(now < priceData.quoteTimestamp + self.staleThreshold!,
message: "Price data's quote timestamp \(priceData.quoteTimestamp) exceeds the staleThreshold "
.concat("\(priceData.quoteTimestamp + self.staleThreshold!) at current timestamp \(now)"))
if let threshold = self.staleThreshold {
assert(now < priceData.baseTimestamp + threshold,
message: "Price data's base timestamp \(priceData.baseTimestamp) exceeds the staleThreshold \(priceData.baseTimestamp + threshold) at current timestamp \(now)")
assert(now < priceData.quoteTimestamp + threshold,
message: "Price data's quote timestamp \(priceData.quoteTimestamp) exceeds the staleThreshold \(priceData.quoteTimestamp + threshold) at current timestamp \(now)")
}

return priceData.fixedPointRate
Expand Down
2 changes: 1 addition & 1 deletion cadence/contracts/connectors/evm/ERC4626PriceOracles.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ access(all) contract ERC4626PriceOracles {
}
let totalAssets = ERC4626Utils.totalAssets(vault: self.vault)
let totalShares = ERC4626Utils.totalShares(vault: self.vault)
if totalAssets == nil || totalShares == nil || totalShares == UInt256(0) {
if totalAssets == nil || totalShares == nil || totalShares == 0 {
return nil
}

Expand Down
8 changes: 2 additions & 6 deletions cadence/contracts/connectors/evm/ERC4626SinkConnectors.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -213,9 +213,7 @@ access(all) contract ERC4626SinkConnectors {
///
access(self)
fun _approveErrorMessage(ufixAmount: UFix64, uintAmount: UInt256, approveRes: EVM.Result): String {
return "Failed to approve ERC4626 vault \(self.vault.toString()) to spend \(ufixAmount) assets \(self.assetEVMAddress.toString()). "
.concat("approvee: \(self.vault.toString()), amount: \(uintAmount). ")
.concat("Error code: \(approveRes.errorCode) Error message: \(approveRes.errorMessage)")
return "Failed to approve ERC4626 vault \(self.vault.toString()) to spend \(ufixAmount) assets \(self.assetEVMAddress.toString()). approvee: \(self.vault.toString()), amount: \(uintAmount). Error code: \(approveRes.errorCode) Error message: \(approveRes.errorMessage)"
}
/// Returns an error message for a failed deposit call
///
Expand All @@ -228,9 +226,7 @@ access(all) contract ERC4626SinkConnectors {
access(self)
fun _depositErrorMessage(ufixAmount: UFix64, uintAmount: UInt256, depositRes: EVM.Result): String {
let coaHex = self.coa.borrow()!.address().toString()
return "Failed to deposit \(ufixAmount) assets \(self.assetEVMAddress.toString()) to ERC4626 vault \(self.vault.toString()). "
.concat("amount: \(uintAmount), to: \(coaHex). ")
.concat("Error code: \(depositRes.errorCode) Error message: \(depositRes.errorMessage)")
return "Failed to deposit \(ufixAmount) assets \(self.assetEVMAddress.toString()) to ERC4626 vault \(self.vault.toString()). amount: \(uintAmount), to: \(coaHex). Error code: \(depositRes.errorCode) Error message: \(depositRes.errorMessage)"
}
}
}
6 changes: 3 additions & 3 deletions cadence/contracts/connectors/evm/EVMAmountUtils.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ access(all) contract EVMAmountUtils {
}

let quantumExp: UInt8 = decimals - 8
let quantum: UInt256 = FlowEVMBridgeUtils.pow(base: 10, exponent: quantumExp)
let quantum = FlowEVMBridgeUtils.pow(base: 10, exponent: quantumExp)
let remainder: UInt256 = amt % quantum
let floored: UInt256 = amt - remainder

Expand All @@ -47,10 +47,10 @@ access(all) contract EVMAmountUtils {
}

let quantumExp: UInt8 = decimals - 8
let quantum: UInt256 = FlowEVMBridgeUtils.pow(base: 10, exponent: quantumExp)
let quantum = FlowEVMBridgeUtils.pow(base: 10, exponent: quantumExp)

let remainder: UInt256 = amt % quantum
var padded: UInt256 = amt
var padded = amt
if remainder != 0 {
padded = amt + (quantum - remainder)
}
Expand Down
6 changes: 2 additions & 4 deletions cadence/contracts/connectors/evm/EVMTokenConnectors.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,7 @@ access(all) contract EVMTokenConnectors {
) {
pre {
FlowEVMBridgeConfig.getEVMAddressAssociated(with: depositVaultType) != nil:
"Provided type \(depositVaultType.identifier) has not been onboarded to the VM bridge - "
.concat("Ensure the type & ERC20 contracts are associated via the VM bridge")
"Provided type \(depositVaultType.identifier) has not been onboarded to the VM bridge - Ensure the type & ERC20 contracts are associated via the VM bridge"
feeSource.getSinkType() == Type<@FlowToken.Vault>() && feeSource.getSourceType() == Type<@FlowToken.Vault>():
"Provided feeSource must provide FlowToken.Vault but provides \(feeSource.getSourceType().identifier)"
}
Expand Down Expand Up @@ -183,8 +182,7 @@ access(all) contract EVMTokenConnectors {
) {
pre {
FlowEVMBridgeConfig.getEVMAddressAssociated(with: withdrawVaultType) != nil:
"Provided type \(withdrawVaultType.identifier) has not been onboarded to the VM bridge - "
.concat("Ensure the type & ERC20 contracts are associated via the VM bridge")
"Provided type \(withdrawVaultType.identifier) has not been onboarded to the VM bridge - Ensure the type & ERC20 contracts are associated via the VM bridge"
DeFiActionsUtils.definingContractIsFungibleToken(withdrawVaultType):
"The contract defining Vault \(withdrawVaultType.identifier) does not conform to FungibleToken contract interface"
coa.check():
Expand Down
23 changes: 8 additions & 15 deletions cadence/contracts/connectors/evm/UniswapV2SwapConnectors.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,9 @@ access(all) contract UniswapV2SwapConnectors {
pre {
path.length >= 2: "Provided path with length of \(path.length) - path must contain at least two EVM addresses)"
FlowEVMBridgeConfig.getTypeAssociated(with: path[0]) == inVault:
"Provided inVault \(inVault.identifier) is not associated with ERC20 at path[0] \(path[0].toString()) - "
.concat("Ensure the type & ERC20 contracts are associated via the VM bridge")
FlowEVMBridgeConfig.getTypeAssociated(with: path[path.length - 1]) == outVault:
"Provided outVault \(outVault.identifier) is not associated with ERC20 at path[\(path.length - 1)] \(path[path.length - 1].toString()) - "
.concat("Ensure the type & ERC20 contracts are associated via the VM bridge")
"Provided inVault \(inVault.identifier) is not associated with ERC20 at path[0] \(path[0].toString()) - Ensure the type & ERC20 contracts are associated via the VM bridge"
FlowEVMBridgeConfig.getTypeAssociated(with: path[path.length - 1]) == outVault:
"Provided outVault \(outVault.identifier) is not associated with ERC20 at path[\(path.length - 1)] \(path[path.length - 1].toString()) - Ensure the type & ERC20 contracts are associated via the VM bridge"
coaCapability.check():
"Provided COA Capability is invalid - provided an active, unrevoked Capability<auth(EVM.Call) &EVM.CadenceOwnedAccount>"
}
Expand Down Expand Up @@ -209,8 +207,7 @@ access(all) contract UniswapV2SwapConnectors {
let id = self.uniqueID?.id?.toString() ?? "UNASSIGNED"
let idType = self.uniqueID?.getType()?.identifier ?? "UNASSIGNED"
let coa = self.borrowCOA()
?? panic("The COA Capability contained by Swapper \(self.getType().identifier) with UniqueIdentifier "
.concat("\(idType) ID \(id) is invalid - cannot perform an EVM swap without a valid COA Capability"))
?? panic("The COA Capability contained by Swapper \(self.getType().identifier) with UniqueIdentifier \(idType) ID \(id) is invalid - cannot perform an EVM swap without a valid COA Capability")

// withdraw FLOW from the COA to cover the VM bridge fee
let bridgeFeeBalance = EVM.Balance(attoflow: 0)
Expand Down Expand Up @@ -241,7 +238,7 @@ access(all) contract UniswapV2SwapConnectors {
// perform the swap
res = self.call(to: self.routerAddress,
signature: "swapExactTokensForTokens(uint256,uint256,address[],address,uint256)", // amountIn, amountOutMin, path, to, deadline (timestamp)
args: [evmAmountIn, UInt256(0), (reverse ? self.addressPath.reverse() : self.addressPath), coa.address(), UInt256(getCurrentBlock().timestamp)],
args: [evmAmountIn, 0, (reverse ? self.addressPath.reverse() : self.addressPath), coa.address(), UInt256(getCurrentBlock().timestamp)],
gasLimit: 1_000_000,
value: 0,
dryCall: false
Expand Down Expand Up @@ -285,7 +282,7 @@ access(all) contract UniswapV2SwapConnectors {
signature: out ? "getAmountsOut(uint,address[])" : "getAmountsIn(uint,address[])",
args: [amount, path],
gasLimit: 1_000_000,
value: UInt(0),
value: 0,
dryCall: true
)
if callRes == nil || callRes!.status != EVM.Status.successful {
Expand Down Expand Up @@ -330,7 +327,7 @@ access(all) contract UniswapV2SwapConnectors {
let calldata = EVM.encodeABIWithSignature(signature, args)
let valueBalance = EVM.Balance(attoflow: value)
if let coa = self.borrowCOA() {
let res: EVM.Result = dryCall
let res = dryCall
? coa.dryCall(to: to, data: calldata, gasLimit: gasLimit, value: valueBalance)
: coa.call(to: to, data: calldata, gasLimit: gasLimit, value: valueBalance)
return res
Expand All @@ -342,10 +339,6 @@ access(all) contract UniswapV2SwapConnectors {
/// Reverts with a message constructed from the provided args. Used in the event of a coa.call() error
access(self)
fun _callError(_ signature: String, _ res: EVM.Result,_ target: EVM.EVMAddress, _ uniqueIDType: String, _ id: String, _ swapperType: Type) {
panic("Call to \(target.toString()).\(signature) from Swapper \(swapperType.identifier) "
.concat("with UniqueIdentifier \(uniqueIDType) ID \(id) failed: \n\t"
.concat("Status value: \(res.status.rawValue)\n\t"))
.concat("Error code: \(res.errorCode)\n\t")
.concat("ErrorMessage: \(res.errorMessage)\n"))
panic("Call to \(target.toString()).\(signature) from Swapper \(swapperType.identifier) with UniqueIdentifier \(uniqueIDType) ID \(id) failed: \n\tStatus value: \(res.status.rawValue)\n\tError code: \(res.errorCode)\n\tErrorMessage: \(res.errorMessage)\n")
}
}
46 changes: 23 additions & 23 deletions cadence/contracts/connectors/evm/UniswapV3SwapConnectors.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ access(all) contract UniswapV3SwapConnectors {

// helper to append address bytes
fun appendAddr(_ a: EVM.EVMAddress) {
let fixed: [UInt8; 20] = a.bytes
let fixed = a.bytes
var i = 0
while i < 20 {
out.append(fixed[i])
Expand Down Expand Up @@ -276,11 +276,11 @@ access(all) contract UniswapV3SwapConnectors {
assert(res.status == EVM.Status.successful, message: "unable to get pool: token0 \(self.tokenPath[0].toString()), token1 \(self.tokenPath[1].toString()), feePath: self.feePath[0]")

// ABI return is one 32-byte word; the last 20 bytes are the address
let word = res.data as! [UInt8]
let word = res.data
if word.length < 32 { panic("getPool: invalid ABI word length") }

let addrSlice = word.slice(from: 12, upTo: 32) // 20 bytes
let addrBytes: [UInt8; 20] = addrSlice.toConstantSized<[UInt8; 20]>()!
let addrBytes = addrSlice.toConstantSized<[UInt8; 20]>()!

return EVM.EVMAddress(bytes: addrBytes)
}
Expand All @@ -305,7 +305,7 @@ access(all) contract UniswapV3SwapConnectors {
fun wordToUIntN(_ w: [UInt8], _ nBits: Int): UInt {
let full = wordToUInt(w)
if nBits >= 256 { return full }
let mask: UInt = (UInt(1) << UInt(nBits)) - UInt(1)
let mask = (1 as UInt << UInt(nBits)) - 1
return full & mask
}
fun words(_ data: [UInt8]): [[UInt8]] {
Expand All @@ -324,7 +324,7 @@ access(all) contract UniswapV3SwapConnectors {
let SEL_LIQUIDITY: [UInt8] = [0x1a, 0x68, 0x65, 0x02]

// Get slot0 (sqrtPriceX96, tick, etc.)
let s0Res: EVM.Result? = self._dryCallRaw(
let s0Res = self._dryCallRaw(
to: poolEVMAddress,
calldata: EVMAbiHelpers.buildCalldata(selector: SEL_SLOT0, args: []),
gasLimit: 1_000_000,
Expand All @@ -333,7 +333,7 @@ access(all) contract UniswapV3SwapConnectors {
let sqrtPriceX96 = wordToUIntN(s0w[0], 160)

// Get current active liquidity
let liqRes: EVM.Result? = self._dryCallRaw(
let liqRes = self._dryCallRaw(
to: poolEVMAddress,
calldata: EVMAbiHelpers.buildCalldata(selector: SEL_LIQUIDITY, args: []),
gasLimit: 300_000,
Expand All @@ -342,10 +342,10 @@ access(all) contract UniswapV3SwapConnectors {

// Calculate price multiplier based on 6% price impact (600 bps)
// Use UInt256 throughout to prevent overflow in multiplication operations
let bps: UInt256 = 600
let Q96: UInt256 = 0x1000000000000000000000000
let sqrtPriceX96_256: UInt256 = UInt256(sqrtPriceX96)
let L_256: UInt256 = UInt256(L)
let bps = 600 as UInt256
let Q96 = 0x1000000000000000000000000 as UInt256
let sqrtPriceX96_256 = UInt256(sqrtPriceX96)
let L_256 = UInt256(L)

var maxAmount: UInt256 = 0
if zeroForOne {
Expand All @@ -360,17 +360,17 @@ access(all) contract UniswapV3SwapConnectors {
// Δx = L * (√P - √P') / (√P * √P')
// Since sqrt prices are in Q96 format: (L * ΔsqrtP * Q96) / (sqrtP * sqrtP')
// This gives us native token0 units after the two Q96 divisions cancel with one Q96 multiplication
let num1: UInt256 = L_256 * bps
let num2: UInt256 = num1 * Q96
let den: UInt256 = UInt256(20000) * sqrtPriceNew
maxAmount = den == 0 ? UInt256(0) : num2 / den
let num1 = L_256 * bps
let num2 = num1 * Q96
let den = 20000 as UInt256 * sqrtPriceNew
maxAmount = den == 0 ? 0 : num2 / den
} else {
// Swapping token1 -> token0 (price increases by maxPriceImpactBps)
// Formula: Δy = L * (√P' - √P)
// Approximation: √P' ≈ √P * (1 + priceImpact/2)
let sqrtMultiplier: UInt256 = 10000 + (bps / 2)
let sqrtPriceNew: UInt256 = (sqrtPriceX96_256 * sqrtMultiplier) / 10000
let deltaSqrt: UInt256 = sqrtPriceNew - sqrtPriceX96_256
let sqrtMultiplier = 10000 as UInt256 + (bps / 2)
let sqrtPriceNew = (sqrtPriceX96_256 * sqrtMultiplier) / 10000
let deltaSqrt = sqrtPriceNew - sqrtPriceX96_256

// Uniswap V3 spec: getAmount1Delta
// Δy = L * (√P' - √P)
Expand All @@ -391,7 +391,7 @@ access(all) contract UniswapV3SwapConnectors {
? "quoteExactInput(bytes,uint256)"
: "quoteExactOutput(bytes,uint256)"

let args: [AnyStruct] = [pathBytes, amount]
let args = [pathBytes, amount]

let res = self._dryCall(self.quoterAddress, callSig, args, 10_000_000)
if res == nil || res!.status != EVM.Status.successful { return nil }
Expand Down Expand Up @@ -456,7 +456,7 @@ access(all) contract UniswapV3SwapConnectors {
)

let coaRef = self.borrowCOA()!
let recipient: EVM.EVMAddress = coaRef.address()
let recipient = coaRef.address()

// optional dev guards
let _chkIn = EVMAbiHelpers.abiUInt256(evmAmountIn)
Expand All @@ -472,7 +472,7 @@ access(all) contract UniswapV3SwapConnectors {
amountOutMinimum: minOutUint
)

let calldata: [UInt8] = EVM.encodeABIWithSignature(
let calldata = EVM.encodeABIWithSignature(
"exactInput((bytes,address,uint256,uint256))",
[exactInputParams]
)
Expand All @@ -491,7 +491,7 @@ access(all) contract UniswapV3SwapConnectors {
)
}
let decoded = EVM.decodeABI(types: [Type<UInt256>()], data: swapRes.data)
let amountOut: UInt256 = decoded.length > 0 ? decoded[0] as! UInt256 : UInt256(0)
let amountOut: UInt256 = decoded.length > 0 ? decoded[0] as! UInt256 : 0

let outVaultType = reverse ? self.inType() : self.outType()
let outTokenEVMAddress =
Expand Down Expand Up @@ -581,9 +581,9 @@ access(all) contract UniswapV3SwapConnectors {
)!
assert(res.status == EVM.Status.successful, message: "token0() call failed")

let word = res.data as! [UInt8]
let word = res.data
let addrSlice = word.slice(from: 12, upTo: 32)
let addrBytes: [UInt8; 20] = addrSlice.toConstantSized<[UInt8; 20]>()!
let addrBytes = addrSlice.toConstantSized<[UInt8; 20]>()!
return EVM.EVMAddress(bytes: addrBytes)
}

Expand Down
Loading