From c013d58f2a873d277ff36a628fc69b4b8d678bcc Mon Sep 17 00:00:00 2001 From: Fabian Scherer Date: Tue, 19 Nov 2024 09:48:46 +0100 Subject: [PATCH] chore: exposes sell function --- .../factories/Immutable_PIM_Factory_v1.sol | 3 +- .../LM_ImmutableMigration_v1.sol | 31 +++- .../ImmutableMigration/UniswapV2Adapter.sol | 32 +--- .../interfaces/ILM_ImmutableMigration_v1.sol | 3 +- .../e2e/ImmutableMigrationE2E.t.sol | 168 +++++------------- .../factories/Immutable_PIM_Factory_v1.t.sol | 36 +++- 6 files changed, 102 insertions(+), 171 deletions(-) diff --git a/src/experimental/factories/Immutable_PIM_Factory_v1.sol b/src/experimental/factories/Immutable_PIM_Factory_v1.sol index 449423eb2..c30fe31d6 100644 --- a/src/experimental/factories/Immutable_PIM_Factory_v1.sol +++ b/src/experimental/factories/Immutable_PIM_Factory_v1.sol @@ -113,7 +113,6 @@ contract Immutable_PIM_Factory_v1 is address fundingManager = address(orchestrator.fundingManager()); // enable bonding curve to mint issuance token issuanceToken.setMinter(fundingManager, true); - issuanceToken.setMinter(address(this), false); // if initial purchase amount set execute first purchase from curve if (initialPurchaseAmount > 0) { @@ -142,6 +141,8 @@ contract Immutable_PIM_Factory_v1 is } catch {} } + // remove factory as minter + issuanceToken.setMinter(address(this), false); // renounce token ownership issuanceToken.renounceOwnership(); diff --git a/src/experimental/modules/ImmutableMigration/LM_ImmutableMigration_v1.sol b/src/experimental/modules/ImmutableMigration/LM_ImmutableMigration_v1.sol index 4a3bb3ecc..3a1a234d6 100644 --- a/src/experimental/modules/ImmutableMigration/LM_ImmutableMigration_v1.sol +++ b/src/experimental/modules/ImmutableMigration/LM_ImmutableMigration_v1.sol @@ -6,6 +6,8 @@ import {IOrchestrator_v1} from "src/orchestrator/interfaces/IOrchestrator_v1.sol"; import {IBondingCurveBase_v1} from "@fm/bondingCurve/abstracts/BondingCurveBase_v1.sol"; +import {IRedeemingBondingCurveBase_v1} from + "@fm/bondingCurve/interfaces/IRedeemingBondingCurveBase_v1.sol"; import {IERC20} from "@oz/token/ERC20/IERC20.sol"; import {IFundingManager_v1} from "@fm/IFundingManager_v1.sol"; import {FM_BC_Bancor_Redeeming_VirtualSupply_v1} from @@ -97,7 +99,9 @@ contract LM_ImmutableMigration_v1 is * @param amountIn The maximum amount of collateral tokens to spend * @param recipient The address to receive the purchased tokens */ - function buyForUpTo(uint amountIn, address recipient) external { + function buyForUpTo(address recipient, uint amountIn, uint minAmountOut) + external + { address fundingManager = address(__Module_orchestrator.fundingManager()); IERC20 collateralToken = __Module_orchestrator.fundingManager().token(); @@ -115,7 +119,7 @@ contract LM_ImmutableMigration_v1 is // Use valid amount to buy from curve if (validAmountIn > 0) { IBondingCurveBase_v1(fundingManager).buyFor( - recipient, validAmountIn, 1 + recipient, validAmountIn, minAmountOut ); } @@ -129,14 +133,35 @@ contract LM_ImmutableMigration_v1 is collateralToken.balanceOf(fundingManager) == migrationThreshold - initialVirtualCollateralSupply ) { - // Close buying on the funding manager + // Close buying & selling on the funding manager IBondingCurveBase_v1(fundingManager).closeBuy(); + IRedeemingBondingCurveBase_v1(fundingManager).closeSell(); // Initiate graduation _graduate(); } } + function sellFor(address recipient, uint amountIn, uint minAmountOut) + external + { + FM_BC_Bancor_Redeeming_VirtualSupply_v1 fundingManager = + FM_BC_Bancor_Redeeming_VirtualSupply_v1( + address(__Module_orchestrator.fundingManager()) + ); + IERC20Issuance_v1 issuanceToken = + IERC20Issuance_v1(fundingManager.getIssuanceToken()); + + // Transfer issuance tokens from sender to this contract + issuanceToken.transferFrom(msg.sender, address(this), amountIn); + + // Approve funding manager to spend issuance token + issuanceToken.approve(address(fundingManager), amountIn); + + // Make sell order + fundingManager.sellTo(recipient, amountIn, minAmountOut); + } + function _checkBuyExceedsThreshold(uint amountIn) internal view diff --git a/src/experimental/modules/ImmutableMigration/UniswapV2Adapter.sol b/src/experimental/modules/ImmutableMigration/UniswapV2Adapter.sol index 16bca7d59..2fdcb3938 100644 --- a/src/experimental/modules/ImmutableMigration/UniswapV2Adapter.sol +++ b/src/experimental/modules/ImmutableMigration/UniswapV2Adapter.sol @@ -33,7 +33,6 @@ contract UniswapV2Adapter is IDexAdapter_v1 { function createLiquidity(address tokenA, address tokenB, address to) external - override returns (uint liquidity) { factory.createPair(tokenA, tokenB); @@ -45,7 +44,7 @@ contract UniswapV2Adapter is IDexAdapter_v1 { IERC20(tokenB).approve(address(router), amountB); // Add liquidity through the router - (,, liquidity) = router.addLiquidity( + router.addLiquidity( tokenA, tokenB, amountA, @@ -56,33 +55,4 @@ contract UniswapV2Adapter is IDexAdapter_v1 { block.timestamp + 10 minutes ); } - - // function createPoolAndAddLiquidity( - // address tokenA, - // address tokenB, - // uint amountADesired, - // uint amountBDesired, - // uint amountAMin, - // uint amountBMin, - // address to - // ) external override returns (uint liquidity) { - // // Create the pair if it doesn't exist - // if (factory.getPair(tokenA, tokenB) == address(0)) { - // factory.createPair(tokenA, tokenB); - // } - - // // - - // // Add liquidity through the router - // (,, liquidity) = router.addLiquidity( - // tokenA, - // tokenB, - // amountADesired, - // amountBDesired, - // amountAMin, - // amountBMin, - // to, - // block.timestamp - // ); - // } } diff --git a/src/experimental/modules/ImmutableMigration/interfaces/ILM_ImmutableMigration_v1.sol b/src/experimental/modules/ImmutableMigration/interfaces/ILM_ImmutableMigration_v1.sol index 71c7fe80e..7ce0bea37 100644 --- a/src/experimental/modules/ImmutableMigration/interfaces/ILM_ImmutableMigration_v1.sol +++ b/src/experimental/modules/ImmutableMigration/interfaces/ILM_ImmutableMigration_v1.sol @@ -5,5 +5,6 @@ import {IERC20PaymentClientBase_v1} from "@lm/interfaces/IERC20PaymentClientBase_v1.sol"; interface ILM_ImmutableMigration_v1 { - function buyForUpTo(uint amountIn, address recipient) external; + function buyForUpTo(address recipient, uint amountIn, uint minAmountOut) + external; } diff --git a/test/experimental/e2e/ImmutableMigrationE2E.t.sol b/test/experimental/e2e/ImmutableMigrationE2E.t.sol index 18d9bca0b..f86003d07 100644 --- a/test/experimental/e2e/ImmutableMigrationE2E.t.sol +++ b/test/experimental/e2e/ImmutableMigrationE2E.t.sol @@ -178,7 +178,7 @@ contract LM_ImmutableMigration_v1E2E is ExtendedE2ETest { if (amountIn == 0) return; // Bound input to range below threshold - amountIn = bound(amountIn, 1, COLLATERAL_MIGRATION_THRESHOLD - 1); + amountIn = bound(amountIn, 1 ether, COLLATERAL_MIGRATION_THRESHOLD - 1); mintAndApprove(amountIn); @@ -187,7 +187,7 @@ contract LM_ImmutableMigration_v1E2E is ExtendedE2ETest { uint buyerIssuanceBalanceBefore = issuanceToken.balanceOf(address(this)); // Execute buy - migrationModule.buyForUpTo(amountIn, address(this)); + migrationModule.buyForUpTo(address(this), amountIn, 1); // Verify balances changed correctly assertLt( @@ -213,7 +213,7 @@ contract LM_ImmutableMigration_v1E2E is ExtendedE2ETest { vm.expectEmit(true, true, true, true); emit IBondingCurveBase_v1.BuyingDisabled(); - migrationModule.buyForUpTo(amountIn, address(this)); + migrationModule.buyForUpTo(address(this), amountIn, 1); assertFalse( FM_BC_Bancor_Redeeming_VirtualSupply_v1( @@ -221,6 +221,12 @@ contract LM_ImmutableMigration_v1E2E is ExtendedE2ETest { ).buyIsOpen(), "Buying should be closed" ); + assertFalse( + FM_BC_Bancor_Redeeming_VirtualSupply_v1( + address(orchestrator.fundingManager()) + ).sellIsOpen(), + "Selling should be closed" + ); assertEq( token.balanceOf(address(this)), amountIn - COLLATERAL_MIGRATION_THRESHOLD, @@ -233,131 +239,37 @@ contract LM_ImmutableMigration_v1E2E is ExtendedE2ETest { ); } - // // Test - // function test_e2e_MigrateLiquidityLifecycle() public { - // //-------------------------------------------------------------------------- - // // Orchestrator Initialization - // //-------------------------------------------------------------------------- - - // // Set WorkflowConfig - // IOrchestratorFactory_v1.WorkflowConfig memory workflowConfig = - // IOrchestratorFactory_v1.WorkflowConfig({ - // independentUpdates: false, - // independentUpdateAdmin: address(0) - // }); - - // // Set Orchestrator - // IOrchestrator_v1 orchestrator = - // _create_E2E_Orchestrator(workflowConfig, moduleConfigurations); - - // // Set FundingManager - // FM_BC_Bancor_Redeeming_VirtualSupply_v1 fundingManager = - // FM_BC_Bancor_Redeeming_VirtualSupply_v1( - // address(orchestrator.fundingManager()) - // ); - - // // Find and Set Migration Manager - // LM_PC_MigrateLiquidity_UniswapV2_v1 migrationManager; - // address[] memory modulesList = orchestrator.listModules(); - // for (uint i; i < modulesList.length; ++i) { - // if ( - // ERC165Upgradeable(modulesList[i]).supportsInterface( - // type(ILM_PC_MigrateLiquidity_UniswapV2_v1).interfaceId - // ) - // ) { - // migrationManager = - // LM_PC_MigrateLiquidity_UniswapV2_v1(modulesList[i]); - // break; - // } - // } - - // // Test Lifecycle - // //-------------------------------------------------------------------------- - - // // 1. Set FundingManager as Minter - // issuanceToken.setMinter(address(fundingManager), true); - - // // 1.1. Set Migration Manager As Minter - // issuanceToken.setMinter(address(migrationManager), true); - - // // 2. Mint Collateral To Buy From the FundingManager - // token.mint(address(this), BUY_FROM_FUNDING_MANAGER_AMOUNT); - - // // 3. Calculate Minimum Amount Out - // uint buf_minAmountOut = fundingManager.calculatePurchaseReturn( - // BUY_FROM_FUNDING_MANAGER_AMOUNT - // ); // buffer variable to store the minimum amount out on calls to the buy and sell functions - - // // 4. Buy from the FundingManager - // vm.startPrank(address(this)); - // { - // // 4.1. Approve tokens to fundingManager. - // token.approve( - // address(fundingManager), BUY_FROM_FUNDING_MANAGER_AMOUNT - // ); - // // 4.2. Deposit tokens, i.e. fund the fundingmanager. - // fundingManager.buy( - // BUY_FROM_FUNDING_MANAGER_AMOUNT, buf_minAmountOut - // ); - // // 4.3. After the deposit, check that the user has received them - // assertTrue( - // issuanceToken.balanceOf(address(this)) > 0, - // "User should have received issuance tokens after deposit" - // ); - // } - // vm.stopPrank(); - - // // 5. Check no pool exists yet - // address lpTokenAddress = - // uniswapFactory.getPair(address(token), address(issuanceToken)); - - // assertEq(lpTokenAddress, address(0), "Pool should not exist yet"); - - // // 6. Set migration manager instance - // ILM_PC_MigrateLiquidity_UniswapV2_v1.LiquidityMigrationConfig memory - // migration = migrationManager.getMigrationConfig(); - - // ILM_PC_MigrateLiquidity_UniswapV2_v1.LiquidityMigrationResult memory - // migrationResult; - - // // 7. Execute migration - // vm.startPrank(address(this)); - // migrationResult = migrationManager.executeMigration(); - // vm.stopPrank(); - - // bool executed = migrationManager.getExecuted(); - - // // 8. Verify pool creation and liquidity - // lpTokenAddress = - // uniswapFactory.getPair(address(token), address(issuanceToken)); - // assertTrue(lpTokenAddress != address(0), "Pool should exist"); - - // // 9.1. Get pair - // IUniswapV2Pair pair = IUniswapV2Pair(lpTokenAddress); - - // // 9.2. Get reserves - // (uint112 reserve0, uint112 reserve1,) = pair.getReserves(); - - // // 9.3. Verify reserves based on token ordering - // if (pair.token0() == address(token)) { - // assertGt(reserve0, 0, "Token reserves should be positive"); - // assertGt(reserve1, 0, "IssuanceToken reserves should be positive"); - // } else { - // assertGt(reserve0, 0, "IssuanceToken reserves should be positive"); - // assertGt(reserve1, 0, "Token reserves should be positive"); - // } - - // // 10. Verify migration completion - // migration = migrationManager.getMigrationConfig(); - // assertTrue(executed, "Migration should be marked as executed"); - - // // 11. Verify LP tokens are received by the migration manager - // assertGt( - // IERC20(migrationResult.lpTokenAddress).balanceOf(address(this)), - // 0, - // "Script should have received LP tokens" - // ); - // } + function test_sellFor(uint amountIn) public { + // Bound input to range below threshold + amountIn = bound(amountIn, 1 ether, COLLATERAL_MIGRATION_THRESHOLD - 1); + mintAndApprove(amountIn); + + assertEq( + issuanceToken.balanceOf(address(this)), + 0, + "Buyer should not hold issuance tokens initially" + ); + + migrationModule.buyForUpTo(address(this), amountIn, 1); + + uint issuanceBalanceBeforeSale = issuanceToken.balanceOf(address(this)); + assertGt( + issuanceBalanceBeforeSale, + 0, + "Buyer should have received issuance tokens" + ); + issuanceToken.approve( + address(migrationModule), issuanceBalanceBeforeSale + ); + + migrationModule.sellFor(address(this), issuanceBalanceBeforeSale, 1); + uint issuanceBalanceAfterSale = issuanceToken.balanceOf(address(this)); + assertEq( + issuanceBalanceAfterSale, + 0, + "Buyer should not hold issuance tokens after sale" + ); + } //-------------------------------------------------------------------------- // Custom Assertions diff --git a/test/experimental/factories/Immutable_PIM_Factory_v1.t.sol b/test/experimental/factories/Immutable_PIM_Factory_v1.t.sol index b55d8c5a2..5d249e00e 100644 --- a/test/experimental/factories/Immutable_PIM_Factory_v1.t.sol +++ b/test/experimental/factories/Immutable_PIM_Factory_v1.t.sol @@ -169,19 +169,38 @@ contract Immutable_PIM_Factory_v1Test is ExtendedE2ETest { address fundingManager = address(orchestrator.fundingManager()); // CHECK: factory DOES NOT have minting rights on token anymore - assertFalse(issuanceToken.allowedMinters(address(factory))); + assertFalse( + issuanceToken.allowedMinters(address(factory)), + "Factory should not have minting rights on token" + ); // CHECK: bonding curve module HAS minting rights on token - assertTrue(issuanceToken.allowedMinters(fundingManager)); + assertTrue( + issuanceToken.allowedMinters(fundingManager), + "Bonding curve module should have minting rights on token" + ); // CHECK: issuance token is renounced - assertEq(issuanceToken.owner(), address(0)); + assertEq( + issuanceToken.owner(), + address(0), + "Issuance token should be renounced" + ); // CHECK: factory HAS admin rights over workflow bytes32 adminRole = orchestrator.authorizer().getAdminRole(); assertTrue( - orchestrator.authorizer().hasRole(adminRole, address(factory)) + orchestrator.authorizer().hasRole(adminRole, address(factory)), + "Factory should have admin rights over workflow" ); // CHECK: initial purchase was executed - assertGt(issuanceToken.balanceOf(workflowAdmin), 0); - assertEq(token.balanceOf(fundingManager), initialPurchaseAmount); + assertGt( + issuanceToken.balanceOf(workflowAdmin), + 0, + "Workflow admin should have received issuance tokens" + ); + assertEq( + token.balanceOf(fundingManager), + initialPurchaseAmount, + "Bonding curve module should have received collateral tokens" + ); // CHECK: migration module has admin role assertMigrationModuleHasAdminRole(orchestrator); @@ -200,7 +219,10 @@ contract Immutable_PIM_Factory_v1Test is ExtendedE2ETest { for (uint i = 0; i < modules.length; i++) { try LM_ImmutableMigration_v1(modules[i]).migrationThreshold() returns (uint threshold) { - if (threshold == COLLATERAL_MIGRATION_THRESHOLD + initialCollateralSupply) { + if ( + threshold + == COLLATERAL_MIGRATION_THRESHOLD + initialCollateralSupply + ) { migrationModule = modules[i]; break; }