Cross-Chain Bridge
USPD is designed to be a versatile stablecoin that operates seamlessly across multiple blockchain networks. While USPD is managing the liquidity on Ethereum mainnet, it is planned that USPD is deployed to most, if not all, other EVM chains as well as non-EVM chains. It should be possible to natively bridge the asset between the chains without sacrificing security.
Problem Statement
To enhance USPD’s utility, it needs to be transferable and usable across multiple blockchain networks (Layer 1s and Layer 2s). This presents several challenges:
- Value Preservation: USPD is a yield-bearing stablecoin. Its user-facing value (USPD amount) is derived from underlying, non-rebasing
cUSPD
share tokens and a dynamicyieldFactor
(USPD =cUSPD
shares *yieldFactor
/FACTOR_PRECISION
). This relationship must be maintained or accurately translated across chains. - Collateral Integrity: All USPD, regardless of the chain it resides on, is backed by collateral held exclusively on the Ethereum mainnet (L1). The bridging mechanism must ensure that USPD on other chains (L2s) is fully backed by an equivalent value locked on L1.
- Supply Accounting: The
totalSupply
of USPD on L1 should accurately reflect the total system liability against the L1 collateral. Tokens moved to L2s should be accounted for as locked on L1, not burned, to maintain this clarity. - Yield Propagation: The yield generated by the collateral on L1 (which increases the
yieldFactor
) should ideally be reflected in the USPD value on L2s, or at least, the bridging mechanism must account foryieldFactor
discrepancies between chains at the time of transfer. - Security: The bridging process must be secure, minimizing trust assumptions and protecting against exploits that could lead to unbacked USPD or loss of user funds.
Solution Architecture Overview
The USPD cross-chain solution uses a lock-and-mint mechanism. To transfer tokens from Ethereum Mainnet (L1) to other chains (L2s/satellites), tokens are locked on L1 and minted on the L2. To return tokens from L2s to L1, tokens are burned on the L2 and unlocked on L1. Smart contracts on L1 and L2s facilitate this process, with off-chain relayers handling message passing.
The BridgeEscrow
contract on L1 is central to this design. It manages a global pool of locked cUSPD
shares for each destination chain.
When USPD is bridged from L1 to an L2:
- A user interacts with a Token Adapter (e.g., for Wormhole, LayerZero).
- The Token Adapter calls
USPDToken.lockForBridging
on L1. USPDToken
calculates the equivalentcUSPD
shares. It then transfers these shares from the Token Adapter to theL1_BridgeEscrow
contract.L1_BridgeEscrow
records these shares as locked for the specifiedtargetChainId
.
To the Token Adapter, this lockForBridging
interaction resembles a “burn” operation. The USPD (as cUSPD
shares) is removed from the Token Adapter’s control and secured by the BridgeEscrow
.
When tokens are bridged from an L2 back to L1:
- L2
cUSPD
shares are burned. This is managed by the L2USPDToken
and L2BridgeEscrow
. - A message is relayed to L1.
- The L1
USPDToken.unlockFromBridging
function is called. This instructsL1_BridgeEscrow
to release the previously lockedcUSPD
shares to the recipient. For the user, this appears like a “mint” operation on L1.
This global lock-per-chain model in BridgeEscrow
allows different bridge providers (Token Adapters) to use a single liquidity and accounting system on L1. This avoids separate locked pools for each bridge.
Core Contracts Involved:
USPDToken.sol
(L1 & L2s):- The primary user-facing ERC20 token.
- On L1, it handles conversions between USPD amounts and
cUSPD
share amounts using the L1PoolSharesConversionRate
. It initiates bridging operations by interacting with theBridgeEscrow
contract. - On L2s, it provides the USPD view over L2
cUSPD
shares, using the L2PoolSharesConversionRate
. It initiates bridging back to L1 by interacting with the L2cUSPDToken
.
cUSPDToken.sol
(L1 & L2s):- The core, non-rebasing ERC20 share token.
- On L1, its shares are transferred to and from the
BridgeEscrow
contract during bridging operations. - On L2s, its shares are minted to users when bridging from L1 and burned when bridging back to L1.
BridgeEscrow.sol
(L1 only):- A dedicated contract responsible for holding all
cUSPD
shares locked on L1 that back USPD on L2s. - Tracks the total
cUSPD
shares bridged out and maintains a per-chain accounting of these shares (bridgedOutSharesPerChain[chainId]
). - On L1, it locks/unlocks
cUSPD
shares. - On L2s, it facilitates burning/minting of L2
cUSPD
shares.
- A dedicated contract responsible for holding all
PoolSharesConversionRate.sol
(L1 & L2s):- Provides the
yieldFactor
used to convert betweencUSPD
shares and USPD amounts. - The L1 instance reflects the true yield from mainnet collateral, calculated from its stETH balance.
- L2 instances store a
_yieldFactor
that is initialized toFACTOR_PRECISION
(1:1) and can be updated by an authorizedYIELD_FACTOR_UPDATER_ROLE
(typically the L2BridgeEscrow
) to reflect the L1yieldFactor
received during bridge operations. This ensures accurate USPD value representation on L2s.
- Provides the
Key Bridging Principle:
The user’s intent is always to bridge a specific USPD value. This uspdAmountIntended
by the user, along with the yieldFactor
of the source chain at the time of initiating the bridge, determines a fixed quantity of cUSPD
shares. This precise cUSPD
share quantity is what is acted upon (locked on L1, minted on L2, burned on L2, or unlocked on L1) across the bridge. The sourceYieldFactor
is always included in the cross-chain message to ensure consistent share calculation on the destination chain.
Bridging Process Detailed
1. L1 (Mainnet) to L2 (e.g., Polygon, Arbitrum)
Steps:
- User Interaction: The user specifies the
uspdAmountToBridge
and thetargetChainId
through a bridge interface or by interacting with an integrated application. - L1
USPDToken.lockForBridging(uspdAmountToBridge, targetChainId)
:- User Approval: The user approves a “Token Adapter” contract (e.g., a specific contract for a bridge provider like Wormhole) to spend their L1
USPDToken
. - Token Adapter Pulls USPD: The Token Adapter calls
L1_USPDToken.transferFrom(userAddress, address(TokenAdapter), uspdAmountToBridge)
, moving the USPD from the user to itself. The Token Adapter now knows theuserAddress
(original initiator). - Token Adapter Initiates Lock: The Token Adapter (which must have
RELAYER_ROLE
onL1_USPDToken
) then callsL1_USPDToken.lockForBridging(uspdAmountToBridge, targetChainId)
.
- User Approval: The user approves a “Token Adapter” contract (e.g., a specific contract for a bridge provider like Wormhole) to spend their L1
- L1
USPDToken.lockForBridging(uspdAmountToBridge, targetChainId)
:- This function is called by the Token Adapter (
msg.sender
is the Token Adapter) and requiresmsg.sender
to have theRELAYER_ROLE
. - It calculates
cUSPDShareAmountToLock = (uspdAmountToBridge * FACTOR_PRECISION) / currentL1YieldFactor
. ThecurrentL1YieldFactor
is fetched from the L1PoolSharesConversionRate
. - Share Transfer to Escrow:
USPDToken
(which hasUSPD_CALLER_ROLE
oncUSPDToken
) callsL1_cUSPDToken.executeTransfer(address(TokenAdapter), address(L1_BridgeEscrow), cUSPDShareAmountToLock)
. This transfers thecUSPD
shares corresponding to the bridged USPD from the Token Adapter’s balance directly to theL1_BridgeEscrow
contract. - It then calls
L1_BridgeEscrow.escrowShares(cUSPDShareAmountToLock, targetChainId, uspdAmountToBridge, currentL1YieldFactor, address(TokenAdapter))
.
- This function is called by the Token Adapter (
- L1
BridgeEscrow.escrowShares(cUSPDShareAmountToLock, targetChainId, uspdAmountIntended, l1YieldFactor, tokenAdapterAddress)
:- This function on the
BridgeEscrow
contract is called by the respective chain’sUSPDToken
. - L1 Behavior:
- The
cUSPD
shares are assumed to have already been transferred toBridgeEscrow
’s address byL1_USPDToken
. - It updates its internal state: increments
totalBridgedOutShares
(total L1 shares locked) andbridgedOutSharesPerChain[targetChainId]
bycUSPDShareAmountToLock
.
- The
- L2 Behavior (Satellite Chain):
- The
cUSPD
shares are assumed to have already been transferred toBridgeEscrow
’s address byL2_USPDToken
. BridgeEscrow
callsL2_cUSPDToken.burn(cUSPDShareAmountToLock)
to burn the shares it just received. (RequiresBridgeEscrow
to haveBURNER_ROLE
on L2cUSPDToken
).- It updates its internal state: increments
totalBridgedOutShares
(total net shares sent from this L2) andbridgedOutSharesPerChain[targetChainId]
(net shares sent from this L2 totargetChainId
).
- The
- It emits a
SharesLockedForBridging
event containingtokenAdapterAddress
,targetChainId
,cUSPDShareAmountToLock
,uspdAmountIntended
, and the source chain’syieldFactor
.
- This function on the
- Off-Chain Bridge Relayer: A relayer service (specific to the chosen bridge provider like Wormhole, LayerZero, etc.) monitors for
SharesLockedForBridging
events on the source chain’sBridgeEscrow
. - Cross-Chain Message to L2:
- The Token Adapter, when initiating the bridge transfer with the bridge provider (e.g., calling Wormhole’s
transferTokens
), specifies theoriginalUserAddress
(or their L2 equivalent) as the L2 recipient. - The relayer detects the
SharesLockedForBridging
event. It correlates this event (e.g., bytokenAdapterAddress
,targetChainId
, amounts) with the message initiated by the Token Adapter. - The relayer constructs and sends the message (or validates/processes the provider’s message) to the target L2. This message includes:
recipientAddress
(the original user’s address on L2, as specified by the Token Adapter).
uspdAmountIntended
(this is the same asuspdAmountToBridge
from L1).sourceL1YieldFactor
(this is thecurrentL1YieldFactor
from L1 at the time of locking).
- The Token Adapter, when initiating the bridge transfer with the bridge provider (e.g., calling Wormhole’s
- L2 Bridge Contract: A corresponding bridge contract on the L2 receives and authenticates the message from the relayer.
- L2
cUSPDToken.mintForBridging(recipientAddress, uspdAmountIntended, sourceL1YieldFactor)
(or similar entry point):- The authenticated L2 bridge contract calls a designated function on the L2
cUSPDToken
(or L2USPDToken
, which then interacts with L2cUSPDToken
). - This function calculates
cUSPDShareAmountToMintOnL2 = (uspdAmountIntended * FACTOR_PRECISION) / sourceL1YieldFactor
. Note that it uses thesourceL1YieldFactor
from the message to ensure the share amount minted on L2 precisely matches the share amount locked on L1 for that transaction. - It mints
cUSPDShareAmountToMintOnL2
to therecipientAddress
on the L2. - It emits an event like
SharesMintedFromBridge
.
- The authenticated L2 bridge contract calls a designated function on the L2
- L2
PoolSharesConversionRate
Update: WhenL2_USPDToken.unlockFromBridging
callsL2_BridgeEscrow.releaseShares
, theL2_BridgeEscrow
(after minting shares onL2_cUSPDToken
) callsL2_PoolSharesConversionRate.updateL2YieldFactor()
with thesourceL1YieldFactor
received in the message. This ensures that the L2 yield factor reflects the L1 factor at the time of the source transaction. (RequiresL2_BridgeEscrow
to haveYIELD_FACTOR_UPDATER_ROLE
onL2_PoolSharesConversionRate
).
2. L2 to L1 (Mainnet)
Steps:
- User Interaction: The user specifies the
uspdAmountToBridge
on the L2 they wish to send back to L1. - L2
USPDToken.burnForBridging(uspdAmountToBridge)
(or similar):- This function is called on the L2
USPDToken
contract. - It fetches the
currentL2YieldFactor
from the L2PoolSharesConversionRate
. - It calculates
cUSPDShareAmountToBurn = (uspdAmountToBridge * FACTOR_PRECISION) / currentL2YieldFactor
. - It then interacts with the L2
cUSPDToken
to burncUSPDShareAmountToBurn
from the user’s L2 balance. The user must have approved L2USPDToken
to manage their L2cUSPD
shares, orUSPDToken
calls a specific burn function oncUSPDToken
. - An event like
SharesBurnedForBridging
is emitted by L2cUSPDToken
(or L2USPDToken
) containinguserAddress
, the L1 chain ID,cUSPDShareAmountToBurn
, the originaluspdAmountToBridge
, andcurrentL2YieldFactor
.
- This function is called on the L2
- Off-Chain Bridge Relayer: The relayer monitors for
SharesBurnedForBridging
events on the L2. - Cross-Chain Message to L1: The relayer constructs and sends a message to L1. This message includes:
recipientAddress
(the user’s address on L1).uspdAmountIntended
(same asuspdAmountToBridge
from L2).sourceL2YieldFactor
(this is thecurrentL2YieldFactor
from L2 at the time of burning).sourceL2ChainId
.
- L1 Bridge Contract: A bridge contract on L1 (or an authorized relayer directly) receives and authenticates the message.
- L1
USPDToken.unlockFromBridging(recipientAddress, uspdAmountIntended, sourceL2YieldFactor, sourceL2ChainId)
:- The authenticated L1 bridge contract (or an authorized relayer, which must have
RELAYER_ROLE
onL1_USPDToken
) calls this function on the L1USPDToken
. - It calculates
cUSPDShareAmountToUnlock = (uspdAmountIntended * FACTOR_PRECISION) / sourceL2YieldFactor
. It uses thesourceL2YieldFactor
from the message. - It then calls
BridgeEscrow.releaseShares(recipientAddress, cUSPDShareAmountToUnlock, sourceL2ChainId, uspdAmountIntended, sourceL2YieldFactor)
.
- The authenticated L1 bridge contract (or an authorized relayer, which must have
- L1
BridgeEscrow.releaseShares(recipientAddress, cUSPDShareAmountToUnlock, sourceL2ChainId, uspdAmountIntended, sourceL2YieldFactor)
:- This function on the
BridgeEscrow
contract handles the “release” or minting of shares on the destination chain. It is called byL1_USPDToken
.BridgeEscrow
verifies thatmsg.sender
is the configureduspdTokenAddress
. - L1 Behavior (Receiving from L2):
- It verifies that
cUSPDShareAmountToUnlock
can be released from L1 escrow (i.e.,bridgedOutSharesPerChain[sourceL2ChainId]
is sufficient). - It executes
L1_cUSPDToken.transfer(recipientAddress, cUSPDShareAmountToUnlock)
to send thecUSPD
shares from theBridgeEscrow
contract back to the user on L1. - It updates its internal state: decrements
totalBridgedOutShares
(total L1 shares locked) andbridgedOutSharesPerChain[sourceL2ChainId]
bycUSPDShareAmountToUnlock
.
- It verifies that
- L2 Behavior (Receiving from L1 or another L2):
BridgeEscrow
callsL2_cUSPDToken.mint(recipientAddress, cUSPDShareAmountToUnlock)
. (RequiresBridgeEscrow
to haveMINTER_ROLE
on L2cUSPDToken
).- It updates its internal state: decrements
totalBridgedOutShares
(total net shares sent from this L2) andbridgedOutSharesPerChain[sourceL2ChainId]
(net shares sent from this L2 tosourceChainId
).
- It emits a
SharesUnlockedFromBridge
event. - The user now holds
cUSPDShareAmountToUnlock
on L1. The actual USPD value of these shares is determined by thecurrentL1YieldFactor
. If L1 yield has accrued while tokens were on L2, the user effectively receives this accrued yield upon bridging back.
- This function on the
Yield Factor Synchronization on L2s
The accuracy of USPD value representation on L2s depends on the L2 PoolSharesConversionRate
reflecting a yieldFactor
that is reasonably synchronized with L1.
- Per-Transaction Update: Including the
sourceL1YieldFactor
in the bridge message (for L1->L2 transfers) and using it to update the L2PoolSharesConversionRate
ensures immediate consistency for that batch of tokens. - Periodic Oracle Update: Alternatively, or additionally, a trusted oracle mechanism could periodically read the L1
yieldFactor
and update L2PoolSharesConversionRate
contracts. This smooths out theyieldFactor
on L2s over time. - Lag: It’s acknowledged that L2
yieldFactor
might slightly lag L1’s. However, the core bridging mechanism ensures that the quantity of shares is preserved based on theyieldFactor
at the moment of the bridging transaction initiation.
Security Considerations
- Role-Based Access Control (RBAC):
USPDToken.lockForBridging
(L1 & L2s) requiresRELAYER_ROLE
for the caller (e.g., Token Adapter).USPDToken.unlockFromBridging
(L1 & L2s) requiresRELAYER_ROLE
for the caller (e.g., Token Adapter or Bridge Relayer Contract).BridgeEscrow.escrowShares
andBridgeEscrow.releaseShares
(L1 & L2s) require the caller to be the configureduspdTokenAddress
.BridgeEscrow
has no owner-protected admin functions after deployment. Its parameters (cUSPDToken
,uspdTokenAddress
,rateContract
) are immutable.BridgeEscrow
on L2s requiresMINTER_ROLE
andBURNER_ROLE
on the L2cUSPDToken
.BridgeEscrow
on L2s requiresYIELD_FACTOR_UPDATER_ROLE
on the L2PoolSharesConversionRate
.- Other sensitive functions (
mintForBridging
in L2cUSPDToken
;updateL2YieldFactor
in L2PoolSharesConversionRate
) must be protected by robust RBAC.
- Bridge Relayer Security: The security of the chosen off-chain bridge relayer system is paramount. Compromised relayers could submit fraudulent messages.
- Message Authentication: Cross-chain messages must be rigorously authenticated on the destination chain to prevent spoofing.
- Per-Chain Limits: Per-chain limits are not enforced by the
BridgeEscrow
contract itself. This responsibility is delegated to the Token Adapter contracts, which will only be grantedRELAYER_ROLE
onUSPDToken
if they implement appropriate velocity/limit controls. - Reentrancy Guards: Apply reentrancy guards where appropriate, especially in functions involving external calls and state changes.
- Audits: Thorough independent security audits of all involved smart contracts and the overall bridging architecture are essential.
- Emergency Pausability: Consider pausable mechanisms for critical functions in case of detected vulnerabilities.
Implementation Plan
This plan outlines the smart contract development and modifications required.
1. BridgeEscrow.sol
(New Contract - L1 Only)
- State Variables:
IERC20 public immutable cUSPDToken;
address public owner;
(orAccessControl
for admin roles)uint256 public totalBridgedOutShares;
mapping(uint256 => uint256) public bridgedOutSharesPerChain; // chainId => sharesAmount
mapping(address => bool) public authorizedRelayers; // For releaseShares
address public uspdTokenAddress; // To verify caller of escrowShares
- Events:
event SharesLockedForBridging(address indexed tokenAdapter, uint256 indexed targetChainId, uint256 cUSPDShareAmount, uint256 uspdAmountIntended, uint256 l1YieldFactor);
event SharesUnlockedFromBridge(address indexed recipient, uint256 indexed sourceChainId, uint256 cUSPDShareAmount, uint256 uspdAmountIntended, uint256 l2YieldFactor);
- Functions:
constructor(address _cUSPDTokenAddress, address _uspdTokenAddress, address _rateContractAddress)
: Sets immutable addresses. No admin/owner.escrowShares(uint256 cUSPDShareAmount, uint256 targetChainId, uint256 uspdAmountIntended, uint256 sourceYieldFactor, address tokenAdapter)
:require(msg.sender == uspdTokenAddress, "Caller not USPDToken");
- If
block.chainid == MAINNET_CHAIN_ID
: Accounts for shares already transferred to it. - If
block.chainid != MAINNET_CHAIN_ID
: CallscUSPDToken.burn(cUSPDShareAmount)
on shares already transferred to it. - Updates
totalBridgedOutShares
andbridgedOutSharesPerChain[targetChainId]
. - Emits
SharesLockedForBridging
.
releaseShares(address recipient, uint256 cUSPDShareAmount, uint256 sourceChainId, uint256 uspdAmountIntended, uint256 sourceYieldFactor)
:require(msg.sender == uspdTokenAddress, "Caller not USPDToken");
- If
block.chainid == MAINNET_CHAIN_ID
: TransferscUSPDToken
from self to recipient. - If
block.chainid != MAINNET_CHAIN_ID
: CallscUSPDToken.mint(recipient, cUSPDShareAmount)
, then callsrateContract.updateL2YieldFactor(sourceYieldFactor)
. - Updates
totalBridgedOutShares
andbridgedOutSharesPerChain[sourceChainId]
. - Emits
SharesUnlockedFromBridge
.
- Admin Functions: None. The contract is not ownable and has no admin-modifiable state post-deployment.
// *
setUspdTokenAddress(address _uspdTokenAddress)
// Removed // *setChainLimit(uint256 chainId, uint256 limit)
// Removed // *transferOwnership(address newOwner)
// Removed // *withdrawERC20(...)
// Removed // *withdrawETH(...)
// Removed (replaced by reverting receive())
2. USPDToken.sol
Modifications (L1)
- State Variables:
address public bridgeEscrowAddress;
- Events (Consider if new events are needed here or if
BridgeEscrow
events suffice):LockForBridgingInitiated(address indexed tokenAdapter, uint256 targetChainId, uint256 uspdAmount, uint256 cUSPDShareAmount);
UnlockFromBridgingInitiated(address indexed recipient, uint256 sourceChainId, uint256 uspdAmountIntended, uint256 sourceChainYieldFactor, uint256 cUSPDShareAmount);
- Functions:
lockForBridging(uint256 uspdAmountToBridge, uint256 targetChainId)
:- Called by the Token Adapter/Relayer (
msg.sender
). Requiresmsg.sender
to haveRELAYER_ROLE
. - Calls
cUSPDToken.executeTransfer(...)
to move shares from Token Adapter toBridgeEscrow
. - Calls
BridgeEscrow.escrowShares(...)
. - Emits
LockForBridgingInitiated
.
- Called by the Token Adapter/Relayer (
unlockFromBridging(address recipient, uint256 uspdAmountIntended, uint256 sourceChainYieldFactor, uint256 sourceChainId)
:- Called by an authorized Relayer (
msg.sender
). Requiresmsg.sender
to haveRELAYER_ROLE
. - Calculates
cUSPDShareAmountToUnlock
. - Calls
BridgeEscrow.releaseShares(...)
. (BridgeEscrow
verifies caller isUSPDToken
). - Emits
UnlockFromBridgingInitiated
.
- Called by an authorized Relayer (
- Admin Functions:
setBridgeEscrowAddress(address _bridgeEscrowAddress)
(onlyRoleDEFAULT_ADMIN_ROLE
)grantRole(RELAYER_ROLE, address relayerOrAdapterAddress)
(onlyRoleDEFAULT_ADMIN_ROLE
)revokeRole(RELAYER_ROLE, address relayerOrAdapterAddress)
(onlyRoleDEFAULT_ADMIN_ROLE
)
3. cUSPDToken.sol
Modifications (L2)
- State Variables (Consider if specific roles are needed for bridge minters):
mapping(address => bool) public authorizedBridgeMinters;
- Events:
SharesMintedFromBridge(address indexed recipient, uint256 cUSPDShareAmount, uint256 uspdAmountIntended, uint256 sourceL1YieldFactor);
- Functions:
mintForBridging(address recipient, uint256 uspdAmountIntended, uint256 sourceL1YieldFactor)
:require(authorizedBridgeMinters[msg.sender], "Caller not bridge minter");
- Calculates
cUSPDShareAmountToMint = (uspdAmountIntended * FACTOR_PRECISION) / sourceL1YieldFactor
. _mint(recipient, cUSPDShareAmountToMint)
.- Emits
SharesMintedFromBridge
.
- Admin Functions:
setAuthorizedBridgeMinter(address minter, bool isAuthorized)
(onlyRoleDEFAULT_ADMIN_ROLE
) // This role is for BridgeEscrow on L2
4. USPDToken.sol
Modifications (L2)
- State Variables (Consider if specific roles are needed for bridge burners):
address public cUSPDTokenAddress_L2;
- Events:
SharesBurnedForBridging(address indexed burner, uint256 targetChainId, uint256 cUSPDShareAmount, uint256 uspdAmountIntended, uint256 l2YieldFactor);
- Functions:
burnForBridging(uint256 uspdAmountToBridge)
:- Requires L2
rateContract
andcUSPDTokenAddress_L2
to be set. - Calculates
cUSPDShareAmountToBurn
using L2rateContract.getYieldFactor()
. - Calls
IcUSPDToken(cUSPDTokenAddress_L2).burn(cUSPDShareAmountToBurn)
. (Requires user to have approved L2USPDToken
to burn their L2cUSPD
shares, orUSPDToken
callsexecuteBurn
oncUSPDToken
). - Emits
SharesBurnedForBridging
.
- Requires L2
- Admin Functions:
setCUSPDTokenAddress(address _address)
setRateContractAddress(address _address)
5. PoolSharesConversionRate.sol
Modifications (L2)
- Functions:
constructor(address _stETHAddress, address _lidoAddress, address _admin)
: On L1, stakes ETH. On L2, initializes_yieldFactor
toFACTOR_PRECISION
. Grants_admin
DEFAULT_ADMIN_ROLE
andYIELD_FACTOR_UPDATER_ROLE
(on L2).getYieldFactor()
: Returns calculated factor on L1, stored_yieldFactor
on L2.updateL2YieldFactor(uint256 newYieldFactor)
: Callable on L2 byYIELD_FACTOR_UPDATER_ROLE
. Updates_yieldFactor
ifnewYieldFactor
is not smaller.
6. Deployment & Configuration Steps:
- Deploy
PoolSharesConversionRate
on L1 (with ETH deposit) and L2s (no deposit), passing admin address. - Deploy
BridgeEscrow
contract on L1 and each L2, providing respective chain’scUSPDToken
address,USPDToken
address, andPoolSharesConversionRate
address. - Call
USPDToken.setBridgeEscrowAddress()
on L1 and each L2 with their respective deployedBridgeEscrow
address. - Grant
RELAYER_ROLE
on eachUSPDToken
instance to the authorized relayer/Token Adapter contract addresses. - On L2s, grant
MINTER_ROLE
andBURNER_ROLE
on L2cUSPDToken
to the L2BridgeEscrow
address. - On L2s, grant
YIELD_FACTOR_UPDATER_ROLE
on L2PoolSharesConversionRate
to the L2BridgeEscrow
address. - Deploy L2 versions of
USPDToken
andcUSPDToken
. - Configure L2
USPDToken
with L2cUSPDToken
and L2PoolSharesConversionRate
addresses.
7. Off-Chain Components:
- Develop or integrate bridge relayer services compatible with the chosen cross-chain messaging protocol (e.g., Wormhole, LayerZero, Axelar). These relayers must:
- Monitor L1
BridgeEscrow
forSharesLockedForBridging
events. - Monitor L2
USPDToken
(orcUSPDToken
) forSharesBurnedForBridging
events. - Securely construct and transmit messages to the destination chain.
- Monitor L1
- Develop a user-friendly bridge interface (UI).
This detailed plan should serve as a solid guideline for development and subsequent audits. Stay tuned for more updates as we progress with the development of this feature.