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
47 changes: 47 additions & 0 deletions cadence/contracts/bridge/FlowEVMBridgeConfig.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,40 @@ contract FlowEVMBridgeConfig {
return self.paused
}

/// StoragePath at which an operator-set read gas limit is stored, if one has been set
///
access(all)
view fun readGasLimitStoragePath(): StoragePath {
return /storage/flowEVMBridgeReadGasLimit
}

/// Returns the read gas limit applied when no operator override has been set
///
access(all)
view fun defaultReadGasLimit(): UInt64 {
return 500_000
}

/// Returns the gas limit the bridge declares for read-only EVM calls whose cost is bounded by the
/// ERC20/ERC721 standards, such as decimals(), balanceOf(address) and ownerOf(uint256).
///
/// A limit smaller than `gasLimit` matters because the FVM admits an EVM call only if the remaining Cadence
/// computation budget covers the *declared* gas limit, not the gas the call actually consumes. At the mainnet
/// execution effort weight for EVM gas, a declared limit of 15,000,000 reserves roughly 1,600 of the 9,999
/// computation units a transaction may spend, so a single cheap read can be refused with most of the budget
/// still unspent. Reads whose cost is not bounded by the standards, notably tokenURI and contractURI, keep
/// using `gasLimit` because they may run arbitrary on-chain metadata generation.
///
/// This is a function reading from account storage rather than a contract field because Cadence rejects a
/// contract update that adds a field to an already deployed contract, and the bridge is live on Mainnet and
/// Testnet. Storage holds a value only once an operator sets one; otherwise the default applies.
///
access(all)
view fun readGasLimit(): UInt64 {
return self.account.storage.copy<UInt64>(from: self.readGasLimitStoragePath())
?? self.defaultReadGasLimit()
}

/// Returns whether operations for a given Type are paused.
///
/// Note: the return type is `Bool?` for API compatibility, but this function currently always returns a
Expand Down Expand Up @@ -503,6 +537,19 @@ contract FlowEVMBridgeConfig {
FlowEVMBridgeConfig.gasLimit = limit
}

/// Sets the gas limit for read-only EVM calls whose cost is bounded by the ERC20/ERC721 standards.
/// The value is kept in account storage rather than a contract field so that this can ship as a
/// contract update; see FlowEVMBridgeConfig.readGasLimit().
///
/// @param limit the new read gas limit
///
access(Gas)
fun setReadGasLimit(_ limit: UInt64) {
let path = FlowEVMBridgeConfig.readGasLimitStoragePath()
let _ = FlowEVMBridgeConfig.account.storage.load<UInt64>(from: path)
FlowEVMBridgeConfig.account.storage.save(limit, to: path)
}

/// Updates the onboarding fee
///
/// @param new: UFix64 - new onboarding fee
Expand Down
6 changes: 3 additions & 3 deletions cadence/contracts/bridge/FlowEVMBridgeHandlers.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ access(all) contract FlowEVMBridgeHandlers {
// Confirm bridge COA's WFLOW balance has incremented by the expected amount
assert(
postBalance - preBalance == uintAmount,
message: "Escrowed WFLOW balance after wrapping does not match requested amount - expected: \(preBalance + uintAmount).toString()) | actual: \(postBalance - preBalance).toString())"
message: "Escrowed WFLOW balance after wrapping does not match requested amount - expected: \((preBalance + uintAmount).toString()) | actual: \((postBalance - preBalance).toString())"
)

// Transfer WFLOW to recipient
Expand Down Expand Up @@ -315,7 +315,7 @@ access(all) contract FlowEVMBridgeHandlers {
)
assert(
ufixAmount > 0.0,
message: "Requested UInt256 amount \(amount.toString()) converted to 0.0 - try bridging a larger amount to avoid UFix64 precision loss during conversion"
message: "Requested UInt256 amount \(amount.toString()) converted to 0.0 - try bridging a larger amount to avoid UFix64 precision loss during conversion"
)

// Transfers WFLOW to bridge COA as escrow
Expand Down Expand Up @@ -351,7 +351,7 @@ access(all) contract FlowEVMBridgeHandlers {
// Confirm bridge COA's FLOW balance has incremented by the expected amount
assert(
UInt256(postBalance - preBalance) == amount,
message: "Escrowed WFLOW balance after unwrapping does not match requested amount - expected: \(UInt256(preBalance) + amount).toString()) | actual: \(postBalance - preBalance).toString())"
message: "Escrowed WFLOW balance after unwrapping does not match requested amount - expected: \((UInt256(preBalance) + amount).toString()) | actual: \((postBalance - preBalance).toString())"
)

// Withdraw escrowed FLOW from bridge COA.
Expand Down
60 changes: 32 additions & 28 deletions cadence/contracts/bridge/FlowEVMBridgeUtils.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ contract FlowEVMBridgeUtils {
signature: "allowsBridging()",
targetEVMAddress: address,
args: [],
gasLimit: FlowEVMBridgeConfig.gasLimit,
gasLimit: FlowEVMBridgeConfig.readGasLimit(),
value: 0.0,
resultTypes: [Type<Bool>()]
)
Expand Down Expand Up @@ -260,7 +260,7 @@ contract FlowEVMBridgeUtils {
signature: "isBridgeDeployed(address)",
targetEVMAddress: self.bridgeFactoryEVMAddress,
args: [evmContractAddress],
gasLimit: FlowEVMBridgeConfig.gasLimit,
gasLimit: FlowEVMBridgeConfig.readGasLimit(),
value: 0.0,
resultTypes: [Type<Bool>()]
)
Expand All @@ -283,7 +283,7 @@ contract FlowEVMBridgeUtils {
signature: "isERC721(address)",
targetEVMAddress: self.bridgeFactoryEVMAddress,
args: [evmContractAddress],
gasLimit: FlowEVMBridgeConfig.gasLimit,
gasLimit: FlowEVMBridgeConfig.readGasLimit(),
value: 0.0,
resultTypes: [Type<Bool>()]
)
Expand All @@ -307,7 +307,7 @@ contract FlowEVMBridgeUtils {
signature: "isERC20(address)",
targetEVMAddress: self.bridgeFactoryEVMAddress,
args: [evmContractAddress],
gasLimit: FlowEVMBridgeConfig.gasLimit,
gasLimit: FlowEVMBridgeConfig.readGasLimit(),
value: 0.0,
resultTypes: [Type<Bool>()]
)
Expand All @@ -330,7 +330,7 @@ contract FlowEVMBridgeUtils {
signature: "isValidAsset(address)",
targetEVMAddress: self.bridgeFactoryEVMAddress,
args: [evmContractAddress],
gasLimit: FlowEVMBridgeConfig.gasLimit,
gasLimit: FlowEVMBridgeConfig.readGasLimit(),
value: 0.0,
resultTypes: [Type<Bool>()]
)
Expand Down Expand Up @@ -475,7 +475,7 @@ contract FlowEVMBridgeUtils {
let isERC20 = self.isERC20(evmContractAddress: evmContractAddress)
assert(
isERC20,
message: "Contract \(evmContractAddress.toString())defines an asset that is not currently supported by the bridge"
message: "Contract \(evmContractAddress.toString()) defines an asset that is not currently supported by the bridge"
)
cadenceContractName = self.deriveBridgedTokenContractName(from: evmContractAddress)
decimals = self.getTokenDecimals(evmContractAddress: evmContractAddress)
Expand Down Expand Up @@ -530,7 +530,7 @@ contract FlowEVMBridgeUtils {
signature: "name()",
targetEVMAddress: evmContractAddress,
args: [],
gasLimit: FlowEVMBridgeConfig.gasLimit,
gasLimit: FlowEVMBridgeConfig.readGasLimit(),
value: 0.0,
resultTypes: [Type<String>()]
)
Expand All @@ -554,7 +554,7 @@ contract FlowEVMBridgeUtils {
signature: "symbol()",
targetEVMAddress: evmContractAddress,
args: [],
gasLimit: FlowEVMBridgeConfig.gasLimit,
gasLimit: FlowEVMBridgeConfig.readGasLimit(),
value: 0.0,
resultTypes: [Type<String>()]
)
Expand All @@ -577,6 +577,8 @@ contract FlowEVMBridgeUtils {
signature: "tokenURI(uint256)",
targetEVMAddress: evmContractAddress,
args: [id],
// Uses the full gasLimit rather than readGasLimit: tokenURI may generate metadata on-chain,
// so its cost is not bounded by the ERC721 standard.
gasLimit: FlowEVMBridgeConfig.gasLimit,
value: 0.0,
resultTypes: [Type<String>()]
Expand All @@ -600,6 +602,8 @@ contract FlowEVMBridgeUtils {
signature: "contractURI()",
targetEVMAddress: evmContractAddress,
args: [],
// Uses the full gasLimit rather than readGasLimit: contractURI may generate metadata on-chain,
// so its cost is not bounded by the ERC721 standard.
gasLimit: FlowEVMBridgeConfig.gasLimit,
value: 0.0,
resultTypes: [Type<String>()]
Expand All @@ -622,7 +626,7 @@ contract FlowEVMBridgeUtils {
signature: "decimals()",
targetEVMAddress: evmContractAddress,
args: [],
gasLimit: FlowEVMBridgeConfig.gasLimit,
gasLimit: FlowEVMBridgeConfig.readGasLimit(),
value: 0.0,
resultTypes: [Type<UInt8>()]
)
Expand Down Expand Up @@ -674,7 +678,7 @@ contract FlowEVMBridgeUtils {
signature: "ownerOf(uint256)",
targetEVMAddress: evmContractAddress,
args: [id],
gasLimit: FlowEVMBridgeConfig.gasLimit,
gasLimit: FlowEVMBridgeConfig.readGasLimit(),
value: 0.0,
resultTypes: [Type<EVM.EVMAddress>()]
)
Expand All @@ -698,7 +702,7 @@ contract FlowEVMBridgeUtils {
signature: "getApproved(uint256)",
targetEVMAddress: evmContractAddress,
args: [ofNFT],
gasLimit: FlowEVMBridgeConfig.gasLimit,
gasLimit: FlowEVMBridgeConfig.readGasLimit(),
value: 0.0,
resultTypes: [Type<EVM.EVMAddress>()]
)
Expand All @@ -725,7 +729,7 @@ contract FlowEVMBridgeUtils {
signature: "exists(uint256)",
targetEVMAddress: erc721Address,
args: [id],
gasLimit: FlowEVMBridgeConfig.gasLimit,
gasLimit: FlowEVMBridgeConfig.readGasLimit(),
value: 0.0,
resultTypes: [Type<Bool>()]
)
Expand All @@ -747,7 +751,7 @@ contract FlowEVMBridgeUtils {
signature: "balanceOf(address)",
targetEVMAddress: evmContractAddress,
args: [owner],
gasLimit: FlowEVMBridgeConfig.gasLimit,
gasLimit: FlowEVMBridgeConfig.readGasLimit(),
value: 0.0,
resultTypes: [Type<UInt256>()]
)
Expand Down Expand Up @@ -782,7 +786,7 @@ contract FlowEVMBridgeUtils {
signature: "totalSupply()",
targetEVMAddress: evmContractAddress,
args: [],
gasLimit: FlowEVMBridgeConfig.gasLimit,
gasLimit: FlowEVMBridgeConfig.readGasLimit(),
value: 0.0,
resultTypes: [Type<UInt256>()]
)
Expand Down Expand Up @@ -854,7 +858,7 @@ contract FlowEVMBridgeUtils {
signature: "getCadenceAddress()",
targetEVMAddress: evmContract,
args: [],
gasLimit: FlowEVMBridgeConfig.gasLimit,
gasLimit: FlowEVMBridgeConfig.readGasLimit(),
value: 0.0,
resultTypes: [Type<String>()]
)
Expand Down Expand Up @@ -885,7 +889,7 @@ contract FlowEVMBridgeUtils {
signature: "getCadenceIdentifier()",
targetEVMAddress: evmContract,
args: [],
gasLimit: FlowEVMBridgeConfig.gasLimit,
gasLimit: FlowEVMBridgeConfig.readGasLimit(),
value: 0.0,
resultTypes: [Type<String>()]
)
Expand All @@ -912,7 +916,7 @@ contract FlowEVMBridgeUtils {
signature: "supportsInterface(bytes4)",
targetEVMAddress: evmContract,
args: [interfaceID],
gasLimit: FlowEVMBridgeConfig.gasLimit,
gasLimit: FlowEVMBridgeConfig.readGasLimit(),
value: 0.0,
resultTypes: [Type<Bool>()]
)
Expand Down Expand Up @@ -940,7 +944,7 @@ contract FlowEVMBridgeUtils {
signature: "supportsInterface(bytes4)",
targetEVMAddress: evmContract,
args: [interfaceID],
gasLimit: FlowEVMBridgeConfig.gasLimit,
gasLimit: FlowEVMBridgeConfig.readGasLimit(),
value: 0.0,
resultTypes: [Type<Bool>()]
)
Expand Down Expand Up @@ -979,7 +983,7 @@ contract FlowEVMBridgeUtils {
signature: "vmBridgeAddress()",
targetEVMAddress: evmContract,
args: [],
gasLimit: FlowEVMBridgeConfig.gasLimit,
gasLimit: FlowEVMBridgeConfig.readGasLimit(),
value: 0.0,
resultTypes: [Type<EVM.EVMAddress>()]
)
Expand Down Expand Up @@ -1585,18 +1589,18 @@ contract FlowEVMBridgeUtils {
erc20Address: EVM.EVMAddress,
protectedTransferCall: fun (): EVM.ResultDecoded
) {
// Ensure the caller is has sufficient balance to bridge the requested amount
let hasSufficientBalance = self.hasSufficientBalance(
amount: amount,
owner: owner,
evmContractAddress: erc20Address
)
assert(hasSufficientBalance, message: "Caller does not have sufficient balance to bridge requested tokens")
let bridgeCOAAddress = self.getBridgeCOAEVMAddress()

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

// Ensure the caller has sufficient balance to bridge the requested amount. The owner's pre-transfer
// balance read above answers this, so calling hasSufficientBalance would repeat an identical
// balanceOf(address) call for a value already in scope.
assert(ownerPreBalance >= amount, message: "Caller does not have sufficient balance to bridge requested tokens")

let bridgePreBalance = self.balanceOf(
owner: self.getBridgeCOAEVMAddress(),
owner: bridgeCOAAddress,
evmContractAddress: erc20Address
)

Expand All @@ -1607,7 +1611,7 @@ contract FlowEVMBridgeUtils {
// Get the resulting balances after transfer
let ownerPostBalance = self.balanceOf(owner: owner, evmContractAddress: erc20Address)
let bridgePostBalance = self.balanceOf(
owner: self.getBridgeCOAEVMAddress(),
owner: bridgeCOAAddress,
evmContractAddress: erc20Address
)

Expand Down
11 changes: 11 additions & 0 deletions cadence/scripts/bridge/get_read_gas_limit.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import "FlowEVMBridgeConfig"

/// Returns the gas limit the bridge declares for read-only EVM calls whose cost is bounded by the
/// ERC20/ERC721 standards, such as decimals(), balanceOf(address) and ownerOf(uint256).
///
/// @returns The current read gas limit.
///
access(all)
fun main(): UInt64 {
return FlowEVMBridgeConfig.readGasLimit()
}
2 changes: 1 addition & 1 deletion cadence/scripts/utils/get_deployer_address.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ fun main(coaHost: Address, deployerTag: String): String {
to: FlowEVMBridgeUtils.getBridgeFactoryEVMAddress(),
signature: "getDeployer(string)",
args: [deployerTag],
gasLimit: FlowEVMBridgeConfig.gasLimit,
gasLimit: FlowEVMBridgeConfig.readGasLimit(),
value: 0,
resultTypes: [Type<EVM.EVMAddress>()]
)
Expand Down
2 changes: 1 addition & 1 deletion cadence/scripts/utils/get_registry_address.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ fun main(coaHost: Address): String {
to: FlowEVMBridgeUtils.getBridgeFactoryEVMAddress(),
signature: "getRegistry()",
args: [],
gasLimit: FlowEVMBridgeConfig.gasLimit,
gasLimit: FlowEVMBridgeConfig.readGasLimit(),
value: 0,
resultTypes: [Type<EVM.EVMAddress>()]
)
Expand Down
30 changes: 30 additions & 0 deletions cadence/tests/flow_evm_bridge_tests.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,36 @@ fun testSetGasLimitSucceeds() {
Test.reset(to: snapshot)
}

access(all)
fun testSetReadGasLimitSucceeds() {

fun getReadGasLimit(): UInt64 {
let readGasLimitResult = executeScript(
"../scripts/bridge/get_read_gas_limit.cdc",
[]
)
Test.expect(readGasLimitResult, Test.beSucceeded())
return readGasLimitResult.returnValue as! UInt64? ?? panic("Problem getting read gas limit")
}

snapshot = getCurrentBlockHeight()

let preReadGasLimit = getReadGasLimit()
let readGasLimit = preReadGasLimit + 1_000

let setReadGasLimitResult = executeTransaction(
"../transactions/bridge/admin/gas/set_read_gas_limit.cdc",
[readGasLimit],
bridgeAccount
)
Test.expect(setReadGasLimitResult, Test.beSucceeded())

let postReadGasLimit = getReadGasLimit()
Test.assertEqual(readGasLimit, postReadGasLimit)

Test.reset(to: snapshot)
}

/* --- ASSET & ACCOUNT SETUP - Configure test accounts with assets to bridge --- */

access(all)
Expand Down
Loading
Loading