feat: Intermediate token migration - #218
Conversation
d356bfd to
8d0c16e
Compare
pblivin0x
left a comment
There was a problem hiding this comment.
I like the start and use of inheritance. May I request we bump the fork block number and add BTC tests
13ff19f to
4399a73
Compare
|
|
||
| /* ============ State Variables ============ */ | ||
|
|
||
| ISetToken public immutable fliToken; // ETH2xFLI or BTC2xFLI |
There was a problem hiding this comment.
Note: This will mean deploying one helper contract per fli. Should be fine imo and is in line with the migration extension (which will also be specific to the respective token). But lmk if youdisagree
| // Fund operator with underlying for subsidy | ||
| const operatorAddress = await operator.getAddress(); | ||
|
|
||
| // Get underlying tokens for seeding and subsidy | ||
| if (config.whale) { | ||
| // WBTC: transfer from whale | ||
| const whaleSigner = await impersonateAccount(config.whale); | ||
| await owner.wallet.sendTransaction({ to: config.whale, value: ether(1) }); | ||
|
|
||
| const aTokenPerNested = await nestedToken.getDefaultPositionRealUnit(config.aToken); | ||
| const underlyingNeededForSeed = config.seedAmount.mul(2).mul(aTokenPerNested).mul(110).div(100).div(ether(1)); | ||
| const totalUnderlyingForSetup = underlyingNeededForSeed.add(config.maxSubsidy); | ||
|
|
||
| await underlyingToken.connect(whaleSigner).transfer(owner.address, totalUnderlyingForSetup); | ||
| await underlyingToken.transfer(operatorAddress, config.maxSubsidy); |
There was a problem hiding this comment.
will the migration need a subsidy?
There was a problem hiding this comment.
Not necessarily. The economics of the migration can be impacted by configuring the liquidity position on uniswap accordingly. Even to the extend where the migration extension will make a significant profit.
There was a problem hiding this comment.
I adjusted the tests to use a 1% pool for the migration instead of 0.3% which makes the migration actually profitable for us, and thereby removes the need for subsidy. (See Migration Economics section in PR summary). Let's discuss offline what exact config we will want to use.
| // Mint seed liquidity position | ||
| const tickLower = -60; | ||
| const tickUpper = 60; | ||
| await intermediateMigrationExtension.mintLiquidityPosition( | ||
| config.seedAmount, | ||
| config.seedAmount, | ||
| ZERO, | ||
| ZERO, | ||
| tickLower, | ||
| tickUpper, | ||
| 3000, | ||
| isNestedToken0, | ||
| ); |
There was a problem hiding this comment.
is the seed liquidity tick important to the migration? or is it just for initialization?
There was a problem hiding this comment.
Only for initialization, the actual liquidity provision is done in the migration itself.
It might be possible to do the initialization in the migration transaction atomically. For now I had just kept it as is to be in line with the previous migration, but I will see what it would take to also move the initialization into the migration tx.
pblivin0x
left a comment
There was a problem hiding this comment.
LGTM, a couple of small questions around subsidy and liquidity position initialization
IntermediateMigrationExtension
Summary
This PR introduces
IntermediateMigrationExtension, a standalone contract for migrating ETH2xFLI and BTC2xFLI from holding their respective 2X tokens directly to holding IntermediateTokens (which wrap the 2X tokens).Motivation
The migration enables a layered token structure where ETH2xFLI and BTC2xFLI hold IntermediateTokens instead of ETH2X/BTC2X directly. This provides flexibility for future upgrades and token management while maintaining the same underlying exposure for token holders.
Key benefit: Streaming fees can be accrued on the IntermediateToken, which was not possible when holding ETH2X/BTC2X directly.
Why a New Extension?
The existing
MigrationExtensionwas designed for the first migration (WETH → ETH2X), where the wrappedSetToken (ETH2X) could be minted directly from aWETH in a single step.The new migration (ETH2X → IntermediateToken) is fundamentally different:
Nested Token Structure: IntermediateToken wraps ETH2X, so we must first issue ETH2X before we can issue IntermediateToken.
Leveraged Underlying: ETH2X is a leveraged token with aWETH (equity) and USDC (debt) components. When issuing ETH2X, we receive USDC that must be sold for WETH. When redeeming, we must buy USDC to repay the debt. The original extension has no debt token handling.
Different Pool Structure: The migration requires an ETH2X/IntermediateToken pool rather than a WETH/wrappedSetToken pool.
Design Options Considered
Option A: WETH/IntermediateToken Pool (Two-Hop Trade)
Option B: ETH2X/IntermediateToken Pool (Single-Hop Trade) ✓
We chose Option B because:
Key Design Decisions
Standalone Implementation
IntermediateMigrationExtensionis a standalone contract that does not inherit fromMigrationExtension. This design was chosen because:MigrationExtension.sol is unchanged - this PR does not modify the original contract.
Atomic Migration
The entire migration executes in a single transaction via
migrateAtomicMorpho():This atomic approach:
Flash Loan Source
The extension uses Morpho flash loans only (0% fee). Aave and Balancer flash loan support was removed to reduce contract size since Morpho provides the same functionality without fees.
IntermediateToken Deployment Options
The IntermediateToken can be deployed on either:
0xa4c8...A3490xD246...603AThe contract supports both via the
useBasicIssuanceflag:true= Use BasicIssuanceModule (original Set Protocol)false= Use DebtIssuanceModule (Index fork)Current expectation: Deploy on Original Set Protocol with BasicIssuanceModule.
Pool Fee Tier & Subsidy Economics
The migration uses a Uniswap V3 pool where the operator is the sole liquidity provider. The pool fee tier determines whether a subsidy is needed:
Why the difference?
With a 0.3% fee tier, the operator captures 0.3% of the trade volume as LP fees, but this doesn't cover:
With a 1% fee tier, the operator captures 1% of the trade volume, which more than covers all costs and generates profit:
ETH2xFLI (1% fee tier):
BTC2xFLI (1% fee tier):
Recommendation: Use 1% fee tier (10000) with tick range ±200 for profitable migration without subsidy.
Operator Transaction Sequence
The operator must execute the following transactions:
Phase 1: Setup (One-time)
Deploy IntermediateToken (anyone can call SetTokenCreator)
Initialize IssuanceModule on IntermediateToken
Initialize StreamingFeeModule on IntermediateToken
Deploy IntermediateMigrationExtension
Add extension to BaseManager (operator only)
Authorize BaseManager initialization (methodologist only)
Phase 2: Migration (Single Transaction)
Required Access
0x6904...E8A4)Implementation
Contract Architecture
IntermediateMigrationExtensionis a standalone contract inheriting from:BaseExtension- ProvidesonlyOperatormodifier andinvokeManagerIERC721Receiver- Required for receiving Uniswap V3 LP NFTsKey Functions
External:
migrateAtomicMorpho(): Single-transaction migration with Morpho flash loanonMorphoFlashLoan(): Flash loan callbackinitialize(): Initialize TradeModule on SetTokensweepTokens(): Recover any stuck tokensonERC721Received(): ERC721 receiver for LP NFTsInternal:
_migrateAtomic(): Core migration logic_issueRequiredPoolTokens(): Issues both ETH2X and IntermediateToken for pool liquidity_mintLiquidityPosition(): Mints Uniswap V3 LP position_decreaseLiquidityPosition(): Removes liquidity and collects fees_redeemExcessWrappedSetToken(): Redeems tokens back to WETH_trade(): Executes trade via TradeModule_sellDebtTokenForUnderlying(): Sells USDC received from issuing ETH2X_buyDebtTokenWithUnderlying(): Buys USDC needed to redeem ETH2X_getWrappedTokenRequiredUnits(): Handles both BasicIssuanceModule and DebtIssuanceModule_issueWrappedToken()/_redeemWrappedToken(): Issue/redeem via configured moduleState Variables
Contract Size
Testing
Integration Test
test/integration/ethereum/intermediateMigrationExtension.spec.tsForks mainnet at block 24219075 where:
0x04b59F9F09750C044D7CfbC177561E409085f0f3)Test executes for both ETH2xFLI and BTC2xFLI:
Test Results
30 passing tests covering both ETH2xFLI and BTC2xFLI:
Migration Tests (per token)
FLIRedemptionHelper Tests (per token, before and after migration)
Streaming Fee Tests (ETH2xFLI only)
ETH2X Exposure Preservation
BTC2X Exposure Preservation
Migration Economics (at test block 24219075, 1% fee tier)
ETH2xFLI:
BTC2xFLI:
Streaming Fee Accrual (1 month simulation)
This demonstrates that streaming fees can be accrued on the IntermediateToken, which was a key motivation for this migration.
How to Run Tests
INTEGRATIONTEST=true yarn test test/integration/ethereum/intermediateMigrationExtension.spec.tsFiles Changed
contracts/adapters/IntermediateMigrationExtension.sol- New standalone migration extensioncontracts/adapters/FLIRedemptionHelper.sol- New redemption helper for FLI holderscontracts/interfaces/external/uniswap-v3/INonfungiblePositionManager.sol- AddedcreateAndInitializePoolIfNecessaryhardhat.config.ts- Optimizer override for IntermediateMigrationExtensionutils/deploys/deployExtensions.ts- Deploy helpers for new contractstest/integration/ethereum/intermediateMigrationExtension.spec.ts- Comprehensive integration testsNote:
MigrationExtension.solis not modified in this PR.FLIRedemptionHelper
A helper contract that allows FLI token holders to redeem their tokens for the underlying 2X tokens (ETH2X/BTC2X). Works transparently before and after migration:
Before Migration:
After Migration:
Key features:
0x69a592D2129415a4A1d1b1E309C17051B7F28d57) which has no hooksisMigrated(),getNestedTokenReceivedOnRedemption()