FastBridgeL2
The FastBridgeL2 contract serves as the Layer 2 coordinator for the FastBridge system, handling the initiation of both native and fast bridge transactions. This contract is deployed on each supported L2 network (Arbitrum, Optimism, Fraxtal) and manages the user-facing interface for bridging crvUSD tokens to Ethereum mainnet.
The contract implements a dual-bridge mechanism that simultaneously initiates both the slow native bridge and the fast LayerZero messaging system. It enforces daily limits, minimum amounts, and manages native token fees to ensure the system operates efficiently while maintaining security and economic sustainability.
FastBridgeL2.vy
The source code for the FastBridgeL2.vy
contract can be found on GitHub. The contract is written using Vyper version 0.4.3
and utilizes a Snekmate module to handle contract ownership.
The source code was audited by ChainSecurity. The full audit report can be found here.
Core Functions¶
The FastBridgeL2 contract provides essential functions for initiating bridge transactions, checking available amounts, and calculating costs. These functions work together to provide users with a seamless bridging experience while maintaining system security and efficiency.
bridge
¶
FastBridgeL2.bridge(_token: IERC20, _to: address, _amount: uint256, _min_amount: uint256=0) -> uint256
Function to initiate a fast bridge transaction for crvUSD tokens from L2 to mainnet. This function handles both the native bridge (slow) and fast bridge (immediate) mechanisms. Users must provide native tokens to cover bridge and messaging fees. The function enforces daily limits and minimum amounts.
Input | Type | Description |
---|---|---|
_token | IERC20 | The token to bridge (only crvUSD is supported) |
_to | address | The receiver on destination chain |
_amount | uint256 | The amount of crvUSD to bridge; 2^256-1 for the whole available balance |
_min_amount | uint256 | Minimum amount to bridge; defaults to 0 |
Returns: The actual amount of crvUSD that was bridged (uint256
).
Source code
interface IMessenger:
def initiate_fast_bridge(_to: address, _amount: uint256, _lz_fee_refund: address): payable
def quote_message_fee() -> uint256: view
CRVUSD: public(immutable(IERC20))
VAULT: public(immutable(address))
INTERVAL: constant(uint256) = 86400 # 1 day
min_amount: public(uint256) # Minimum amount to initiate bridge. Might be costy to claim on Ethereum
limit: public(uint256) # Maximum amount to bridge in an INTERVAL, so there's no queue to resolve to claim on Ethereum
bridged: public(HashMap[uint256, uint256]) # Amounts of bridge coins per INTERVAL
bridger: public(IBridger)
messenger: public(IMessenger)
@external
@payable
def bridge(_token: IERC20, _to: address, _amount: uint256, _min_amount: uint256=0) -> uint256:
"""
@notice Bridge crvUSD
@param _token The token to bridge (only crvUSD is supported)
@param _to The receiver on destination chain
@param _amount The amount of crvUSD to deposit, 2^256-1 for the whole available balance
@param _min_amount Minimum amount to bridge
@return Bridged amount
"""
assert _token == CRVUSD, "Not supported"
assert _to != empty(address), "Bad receiver"
amount: uint256 = _amount
if amount == max_value(uint256):
amount = min(staticcall CRVUSD.balanceOf(msg.sender), staticcall CRVUSD.allowance(msg.sender, self))
# Apply daily limit
available: uint256 = self._get_available()
amount = min(amount, available)
assert amount >= _min_amount
assert extcall CRVUSD.transferFrom(msg.sender, self, amount, default_return_value=True)
self.bridged[block.timestamp // INTERVAL] += amount
bridger_cost: uint256 = self.bridger_cost()
messaging_cost: uint256 = self.messaging_cost()
assert msg.value >= bridger_cost + messaging_cost, "Insufficient msg.value"
# Initiate bridge transaction using native bridge
extcall self.bridger.bridge(CRVUSD, VAULT, amount, self.min_amount, value=bridger_cost)
# Message for VAULT to release amount while waiting
extcall self.messenger.initiate_fast_bridge(_to, amount, msg.sender, value=messaging_cost)
# Refund the rest of the msg.value
if msg.value > bridger_cost + messaging_cost:
send(msg.sender, msg.value - bridger_cost - messaging_cost)
log IBridger.Bridge(token=_token, sender=msg.sender, receiver=_to, amount=amount)
return amount
allowed_to_bridge
¶
FastBridgeL2.allowed_to_bridge(_ts: uint256=block.timestamp) -> (uint256, uint256)
Checks how much crvUSD can be bridged at a specific timestamp, considering daily limits and minimum requirements. Returns both the minimum and maximum amounts that can be bridged in the current interval.
Input | Type | Description |
---|---|---|
_ts | uint256 | Timestamp at which to check (default: current block timestamp) |
Returns: A tuple of (minimum_amount, maximum_amount) that can be bridged ((uint256, uint256)
).
Source code
INTERVAL: constant(uint256) = 86400 # 1 day
min_amount: public(uint256) # Minimum amount to initiate bridge. Might be costly to claim on Ethereum
limit: public(uint256) # Maximum amount to bridge in an INTERVAL, so there's no queue to resolve to claim on Ethereum
bridged: public(HashMap[uint256, uint256]) # Amounts of bridge coins per INTERVAL
@external
@view
def allowed_to_bridge(_ts: uint256=block.timestamp) -> (uint256, uint256):
"""
@notice Get interval of allowed amounts to bridge
@param _ts Timestamp at which to check (current by default)
@return (minimum, maximum) amounts allowed to bridge
"""
if _ts < block.timestamp: # outdated
return (0, 0)
available: uint256 = self._get_available(_ts)
# Funds transferred to the contract are lost :(
min_amount: uint256 = self.min_amount
if available < min_amount: # Not enough for bridge initiation
return (0, 0)
return (min_amount, available)
@view
def _get_available(ts: uint256=block.timestamp) -> uint256:
limit: uint256 = self.limit
bridged: uint256 = self.bridged[ts // INTERVAL]
return limit - min(bridged, limit)
cost
¶
FastBridgeL2.cost() -> uint256
Calculates the total native token cost required for a bridge transaction. This includes both the native bridge fee and the fast messaging fee. Users must send this amount as msg.value
when calling the bridge()
function.
Returns: Total native token amount needed for the bridge transaction (uint256
).
Source code
implements: IBridger
interface IMessenger:
def initiate_fast_bridge(_to: address, _amount: uint256, _lz_fee_refund: address): payable
def quote_message_fee() -> uint256: view
bridger: public(IBridger)
messenger: public(IMessenger)
@external
@view
def cost() -> uint256:
"""
@notice Quote messaging fee in native token. This value has to be provided
as msg.value when calling bridge(). This is not fee in crvUSD that is paid to the vault!
@return Native token amount needed for bridge tx
"""
return self.messaging_cost() + self.bridger_cost()
@internal
@view
def messaging_cost() -> uint256:
"""
Messaging cost to pass message to VAULT (Fast Bridge)
@return Native token amount needed for messenger
"""
return staticcall self.messenger.quote_message_fee()
@internal
@view
def bridger_cost() -> uint256:
"""
Bridger cost to bridge crvUSD to VAULT (Native Bridge)
@return Native token amount needed for bridger
"""
return staticcall self.bridger.cost()
Variables¶
The FastBridgeL2 contract maintains several important state variables that control its operation, track bridged amounts, manage limits, and store contract addresses. These variables work together to ensure proper functioning of the bridge system while maintaining security and economic sustainability.
min_amount
¶
FastBridgeL2.min_amount() -> uint256
The minimum amount of crvUSD required to initiate a bridge transaction. This threshold exists because claiming small amounts on Ethereum can be expensive due to gas costs. Can be changed using the set_min_amount
function.
Returns: Minimum crvUSD amount required for bridging (uint256
).
Source code
limit
¶
FastBridgeL2.limit() -> uint256
The maximum amount of crvUSD that can be bridged within a 24-hour interval. This daily limit prevents overwhelming the Ethereum claim queue and ensures smooth processing of bridge transactions. Can be changed using the set_limit
function.
Returns: Maximum crvUSD amount that can be bridged per day (uint256
).
Source code
bridged
¶
FastBridgeL2.bridged(arg0: uint256) -> uint256
Tracks the total amount of crvUSD that has been bridged in each 24-hour interval. The key is the timestamp divided by the interval (86400 seconds), and the value is the cumulative amount bridged.
Input | Type | Description |
---|---|---|
arg0 | uint256 | Time interval key (timestamp // 86400) |
Returns: Total crvUSD amount bridged in the specified time interval (uint256
).
bridger
¶
FastBridgeL2.bridger() -> IBridger
The contract responsible for handling the native bridge transaction that actually moves crvUSD from L2 to mainnet. This is the slower but reliable bridge mechanism. Can be changed using the set_bridger
function.
Returns: Address of the bridger contract (IBridger
).
messenger
¶
FastBridgeL2.messenger() -> IMessenger
The contract responsible for sending fast messages to the mainnet vault, enabling immediate access to bridged funds while the native bridge transaction is still pending. Can be changed using the set_messenger
function.
Returns: Address of the messenger contract (IMessenger
).
CRVUSD
¶
FastBridgeL2.CRVUSD() -> IERC20
The crvUSD token contract address on the L2 network. This is the token that gets bridged from L2 to mainnet. The address is set during deployment and cannot be changed.
Returns: crvUSD token contract address (IERC20
).
VAULT
¶
FastBridgeL2.VAULT() -> address
The mainnet vault contract address where bridged crvUSD tokens are sent. This is the destination for both the native bridge and the fast bridge mechanisms. The address is set during deployment and cannot be changed.
Returns: Mainnet vault contract address (address
).
version
¶
FastBridgeL2.version() -> String[8]
The version identifier for this contract implementation. This helps track which version of the contract is deployed and can be used for upgrade compatibility checks.
Returns: Contract version string (String[8]
).
Owner Functions¶
The FastBridgeL2 contract includes several administrative functions that allow the contract owner to manage system parameters, update contract addresses, and configure operational settings. These functions are protected by ownership checks to ensure only authorized personnel can make critical changes to the system.
set_min_amount
¶
FastBridgeL2.set_min_amount(_min_amount: uint256)
Guarded Method
This function is only callable by the owner
of the contract.
Updates the minimum amount of crvUSD required to initiate a bridge transaction. Only the contract owner can call this function. This helps prevent users from bridging amounts that would be uneconomical to claim on mainnet.
Input | Type | Description |
---|---|---|
_min_amount | uint256 | New minimum amount required for bridging |
Returns: None.
Source code
min_amount: public(uint256) # Minimum amount to initiate bridge. Might be costly to claim on Ethereum
@external
def set_min_amount(_min_amount: uint256):
"""
@notice Set minimum amount allowed to bridge
@param _min_amount Minimum amount
"""
ownable._check_owner()
self.min_amount = _min_amount
log SetMinAmount(min_amount=_min_amount)
set_limit
¶
FastBridgeL2.set_limit(_limit: uint256)
Guarded Method
This function is only callable by the owner
of the contract.
Updates the daily limit for crvUSD bridging. Only the contract owner can call this function. This limit prevents overwhelming the Ethereum claim queue and ensures smooth processing of bridge transactions.
Input | Type | Description |
---|---|---|
_limit | uint256 | New daily limit for crvUSD bridging |
Returns: None.
Source code
limit: public(uint256) # Maximum amount to bridge in an INTERVAL, so there's no queue to resolve to claim on Ethereum
@external
def set_limit(_limit: uint256):
"""
@notice Set new limit
@param _limit Limit on bridging per INTERVAL
"""
ownable._check_owner()
self.limit = _limit
log SetLimit(limit=_limit)
set_bridger
¶
FastBridgeL2.set_bridger(_bridger: IBridger)
Guarded Method
This function is only callable by the owner
of the contract.
Updates the bridger contract that handles the native bridge transaction. Only the contract owner can call this function. The function also updates the crvUSD token approval to the new bridger contract.
Input | Type | Description |
---|---|---|
_bridger | IBridger | New bridger contract address |
Returns: None.
Source code
bridger: public(IBridger)
@external
def set_bridger(_bridger: IBridger):
"""
@notice Set new bridger
@param _bridger Contract initiating actual bridge transaction
"""
ownable._check_owner()
assert _bridger != empty(IBridger), "Bad bridger value"
assert extcall CRVUSD.approve(self.bridger.address, 0, default_return_value=True)
assert extcall CRVUSD.approve(_bridger.address, max_value(uint256), default_return_value=True)
self.bridger = _bridger
log SetBridger(bridger=_bridger)
set_messenger
¶
FastBridgeL2.set_messenger(_messenger: IMessenger)
Guarded Method
This function is only callable by the owner
of the contract.
Updates the messenger contract that handles fast message delivery to the mainnet vault. Only the contract owner can call this function. This allows for upgrading the fast bridge mechanism without changing the main contract.
Input | Type | Description |
---|---|---|
_messenger | IMessenger | New messenger contract address |
Returns: None.
Source code
messenger: public(IMessenger)
@external
def set_messenger(_messenger: IMessenger):
"""
@notice Set new messenger
@param _messenger Contract passing bridge tx fast
"""
ownable._check_owner()
assert _messenger != empty(IMessenger), "Bad messenger value"
self.messenger = _messenger
log SetMessenger(messenger=_messenger)