Stack: Solidity ^0.8.24, Foundry, OpenZeppelin, Chainlink CCIP, GitHub Actions, semantic-release · Repo: Axeloooo/Cross-Chain-Rebase-Token
Problem
A rebase token keeps per-user state: an interest rate and a last-updated timestamp. A plain burn-and-mint bridge would not carry that rate across. This project builds the token, a vault to mint it, and a CCIP token pool that sends the user's rate with the transfer so the destination chain mints with the same rate.
The rule in the contract comments is that the global rate can only decrease, and each depositor locks in the global rate at deposit time, so earlier depositors keep higher rates. It targets Sepolia and Arbitrum Sepolia.
Architecture diagram
Sepolia
-------
User --ETH deposit--> Vault --mint at global rate--> RebaseToken
User <--ETH-- Vault <--redeem: burn tokens---------- RebaseToken
User --approve token + LINK, ccipSend--> CCIP Router
CCIP Router --lockOrBurn--> RebaseTokenPool
1. reads the sender's rate from RebaseToken
2. burns the tokens
3. returns destPoolData = abi.encode(rate)
|
| CCIP message (OnRamp / OffRamp omitted)
v
Arbitrum Sepolia
----------------
CCIP Router --releaseOrMint--> RebaseTokenPool
1. decodes the rate from destPoolData
2. mints to the receiver with that rate --> RebaseToken
(same user rate as on Sepolia)Vault accepts ETH, mints RebaseToken at the current global rate and burns on redeem before sending ETH back. RebaseToken stores principal in ERC-20 storage and overrides balanceOf to return principal plus linear interest; interest is minted lazily when the account mints, burns or transfers. Foundry scripts deploy, grant roles, register the pool, configure each chain pair and send a bridge transaction. The vault is deployed on Sepolia only; Arbitrum Sepolia has the token and pool.
Key decisions
- Lazy, linear interest. Balances are computed on read from principal, rate and elapsed time (1e18 precision, default 5e10 per second), so no keeper job is needed.
- Per-user rate snapshots. The rate is stored at mint time, and a transfer to an empty recipient copies the sender's rate.
- A one-way rate. A custom error blocks any non-decrease of the global rate (the check is
>=), and a fuzz test covers it. - Carry the rate in
destPoolData. The user's rate rides inside the CCIP token transfer instead of a second message. - Separate roles. Mint and burn rights live in an
AccessControlrole distinct from ownership; the vault and the pool each receive it. - A max-uint sentinel for "whole balance". Burn, transfer,
transferFromand redeem accepttype(uint256).max, which resolves to the full, interest-inclusive balance at execution time.
Challenges
- Reading the rate before burning. The pool must read the sender's rate with
getUserInterestRate(originalSender)before it burns (lockOrBurn), andreleaseOrMintmust pass the decoded rate intomint; getting that order wrong loses the rate. - Fork-based tests. The cross-chain test runs two live forks, makes the CCIP simulator persistent across them, and must call it with the source fork active. It reads
SEPOLIA_RPC_URLandARBITRUM_SEPOLIA_RPC_URLfrom the environment. - Dependency wiring. The OpenZeppelin and Chainlink submodules need explicit remappings in
foundry.toml, including versioned aliases for one OpenZeppelin checkout. - Design limits worth stating. The vault has no yield source of its own, so accrued interest is only redeemable if ETH rewards are sent to it (the tests do this), and a new mint resets an existing holder's stored rate to the current global rate.
Results
- The repository holds the full flow: vault, rebase token, CCIP pool, and deploy, configure and bridge scripts.
RebaseToken.t.solhas nine tests, seven of them fuzzed: linear accrual within one wei, redeem immediately and after time, transfers, rate-decrease-only, access control, principal balance and vault address.CrossChain.t.soldeposits on Sepolia, bridges to Arbitrum Sepolia, warps time, bridges back and asserts balances and equal interest rates on both chains.- CI runs
forge fmt --check,forge build --sizesandforge test; semantic-release runs onmain.