diff --git a/.gitmodules b/.gitmodules index 6ddf2e2..cd2c911 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,6 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std -[submodule "lib/aave-address-book"] - path = lib/aave-address-book - url = https://github.com/bgd-labs/aave-address-book [submodule "lib/aave-helpers"] path = lib/aave-helpers url = https://github.com/bgd-labs/aave-helpers @@ -14,7 +11,6 @@ path = lib/stk-no-cooldown url = https://github.com/bgd-labs/aave-stk-v1-5 branch = feat/post-slashing -[submodule "lib/aave-stk-gov-v3"] - path = lib/aave-stk-gov-v3 - url = https://github.com/bgd-labs/aave-stk-gov-v3 - branch = feat/new-deployment +[submodule "lib/stake-token"] + path = lib/stake-token + url = https://github.com/bgd-labs/stake-token diff --git a/Makefile b/Makefile index 8f5077a..a8ac193 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ update:; forge update build :; forge build --sizes test :; forge test -vvv -deploy-ledger :; forge script ${contract} --rpc-url ${chain} $(if ${dry},--sender 0x25F2226B597E8F9514B3F68F00f494cF4f286491 -vvvv,--broadcast --ledger --mnemonics foo --mnemonic-indexes ${MNEMONIC_INDEX} --sender ${LEDGER_SENDER} --verify -vvvv) +deploy-ledger :; forge script ${contract} --rpc-url ${chain} $(if ${dry},--sender 0x25F2226B597E8F9514B3F68F00f494cF4f286491 -vvvv,--broadcast --ledger --mnemonics foo --mnemonic-indexes ${MNEMONIC_INDEX} --sender ${LEDGER_SENDER} --verify -vvvv --resume) # Utilities download :; cast etherscan-source --chain ${chain} -d src/etherscan/${chain}_${address} ${address} @@ -39,3 +39,5 @@ diff-snapshot : npm run clean-storage-report noCooldownStakedToken make git-diff before=reports/currentStakedToken.md after=reports/newStakedToken.md out=NewVersion_storageDiff.md make git-diff before=reports/currentStakedToken.md after=reports/noCooldownStakedToken.md out=NoCooldown_storageDiff.md + +deploy-oracles-ledger :; make deploy-ledger contract=scripts/00_PriceOracles.s.sol:DeployOracles chain=mainnet diff --git a/lib/aave-address-book b/lib/aave-address-book deleted file mode 160000 index b87a51d..0000000 --- a/lib/aave-address-book +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b87a51d014167effaaa7f6f7e33562b223b3d6b2 diff --git a/lib/aave-helpers b/lib/aave-helpers index 2152246..6cc788d 160000 --- a/lib/aave-helpers +++ b/lib/aave-helpers @@ -1 +1 @@ -Subproject commit 2152246b8a3c1104cf96e83c9b87ffe5f9b8ba69 +Subproject commit 6cc788d1346e6bcaf8f02878841eb74662c87c17 diff --git a/lib/aave-stk-gov-v3 b/lib/aave-stk-gov-v3 deleted file mode 160000 index 748cde3..0000000 --- a/lib/aave-stk-gov-v3 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 748cde3720585f9aa15ef62166b36f4144617a4d diff --git a/lib/stake-token b/lib/stake-token new file mode 160000 index 0000000..f42de81 --- /dev/null +++ b/lib/stake-token @@ -0,0 +1 @@ +Subproject commit f42de811d1ef831545446cc357b54e187ccd87a2 diff --git a/remappings.txt b/remappings.txt index cdc62b7..b550a55 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,11 +1,11 @@ -aave-address-book/=lib/aave-address-book/src/ +aave-address-book/=lib/aave-helpers/lib/aave-address-book/src/ aave-helpers/=lib/aave-helpers/src/ ds-test/=lib/forge-std/lib/ds-test/src/ -forge-std/=lib/forge-std/src/ +forge-std/=lib/aave-helpers/lib/forge-std/src/ solidity-utils/=lib/solidity-utils/src/ -aave-v3-periphery/=lib/aave-v3-periphery/contracts/ -aave-v3-core/=lib/aave-address-book/lib/aave-v3-core/ -aave-stk-gov-v3/=lib/aave-stk-gov-v3/src/ +aave-v3-periphery/=lib/aave-helpers/lib/aave-v3-periphery/contracts/ +aave-v3-core/=lib/aave-helpers/lib/aave-address-book/lib/aave-v3-core/ +stake-token/=lib/stake-token/src/ stk-no-cooldown/=lib/stk-no-cooldown/src/ -aave-token-v3/=lib/aave-stk-gov-v3/lib/aave-token-v3/src -openzeppelin-contracts/=lib/aave-stk-gov-v3/lib/aave-token-v3/lib/openzeppelin-contracts +aave-token-v3/=lib/stake-token/lib/aave-token-v3/src +openzeppelin-contracts/=lib/stake-token/lib/openzeppelin-contracts diff --git a/scripts/01_DeployStkAbptV2Impl.sol b/scripts/01_DeployStkAbptV2Impl.sol index 9ea532b..87a6ccb 100644 --- a/scripts/01_DeployStkAbptV2Impl.sol +++ b/scripts/01_DeployStkAbptV2Impl.sol @@ -1,49 +1,47 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {AaveMisc} from 'aave-address-book/AaveMisc.sol'; -import {AaveGovernanceV2} from 'aave-address-book/AaveGovernanceV2.sol'; +import {MiscEthereum} from 'aave-address-book/MiscEthereum.sol'; import {AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; import {EthereumScript} from 'aave-helpers/ScriptUtils.sol'; import {StakedTokenV3 as StakedTokenV3NoCooldown, IERC20 as IERC20NoCooldown} from 'stk-no-cooldown/contracts/StakedTokenV3.sol'; -import {StakedTokenV3} from 'aave-stk-gov-v3/contracts/StakedTokenV3.sol'; +import {StakeToken} from 'stake-token/contracts/StakeToken.sol'; import {IERC20} from 'openzeppelin-contracts/contracts/token/ERC20/IERC20.sol'; import {ITransparentProxyFactory} from 'solidity-utils/contracts/transparent-proxy/interfaces/ITransparentProxyFactory.sol'; import {IWeightedPool} from '../src/interfaces/IWeightedPool.sol'; import {GenericProposal} from '../src/libs/GenericProposal.sol'; import {Addresses} from '../src/libs/Addresses.sol'; -/** - * This is an completely empty contract. - * It just exists to initialize Proxies with "some implementation" - */ -contract PlaceholderContract { - -} - /** * @notice Deploys a the implementation for the pool with the bricked initialize. */ contract DeployImpl is EthereumScript { - function _deploy() public returns (address, address, address) { + function _deploy() public returns (address, address) { address stkABPTV2Impl = address( - new StakedTokenV3( + new StakeToken( + 'stk AAVE/wstETH BPTv2', IERC20(Addresses.ABPT_V2), IERC20(AaveV3EthereumAssets.AAVE_UNDERLYING), GenericProposal.UNSTAKE_WINDOW, GenericProposal.REWARDS_VAULT, - GenericProposal.EMISSION_MANAGER, - GenericProposal.DISTRIBUTION_DURATION + GenericProposal.EMISSION_MANAGER ) ); - address tokenProxy = ITransparentProxyFactory(AaveMisc.TRANSPARENT_PROXY_FACTORY_ETHEREUM) - .createDeterministic( - address(new PlaceholderContract()), - AaveMisc.PROXY_ADMIN_ETHEREUM, - bytes(''), - 'ABPT_V2' - ); + address tokenProxy = ITransparentProxyFactory(MiscEthereum.TRANSPARENT_PROXY_FACTORY).create( + address(stkABPTV2Impl), + MiscEthereum.PROXY_ADMIN, + abi.encodeWithSelector( + StakeToken.initialize.selector, + 'stk AAVE/wstETH BPTv2', // name + 'stkAAVEwstETHBPTv2', // symbol + GenericProposal.SLASHING_ADMIN, + GenericProposal.COOLDOWN_ADMIN, + GenericProposal.CLAIM_HELPER, + GenericProposal.MAX_SLASHING, + GenericProposal.COOLDOWN_SECONDS + ) + ); return ( address( @@ -56,7 +54,6 @@ contract DeployImpl is EthereumScript { GenericProposal.DISTRIBUTION_DURATION ) ), - stkABPTV2Impl, tokenProxy ); } diff --git a/scripts/02_DeployPayload.sol b/scripts/02_DeployPayload.sol index 0f2d3de..d0e956a 100644 --- a/scripts/02_DeployPayload.sol +++ b/scripts/02_DeployPayload.sol @@ -9,20 +9,14 @@ import {ProposalPayload} from '../src/contracts/ProposalPayload.sol'; */ contract DeployPayload is EthereumScript { address internal constant STK_ABPT_V1_IMPL = address(0); - address internal constant STK_ABPT_V2_IMPL = address(0); - function _deploy( - address stkAbptV1Impl, - address stkAbptV2Impl, - address stkAbptV2Proxy - ) public returns (address) { + function _deploy(address stkAbptV1Impl, address stkAbptV2Proxy) public returns (address) { require(stkAbptV1Impl != address(0)); - require(stkAbptV2Impl != address(0)); require(stkAbptV2Proxy != address(0)); - return address(new ProposalPayload(stkAbptV1Impl, stkAbptV2Impl, stkAbptV2Proxy)); + return address(new ProposalPayload(stkAbptV1Impl, stkAbptV2Proxy)); } function run() external broadcast { - _deploy(STK_ABPT_V1_IMPL, STK_ABPT_V2_IMPL, address(0)); + _deploy(STK_ABPT_V1_IMPL, address(0)); } } diff --git a/src/contracts/ProposalPayload.sol b/src/contracts/ProposalPayload.sol index 109fb57..9d769c1 100644 --- a/src/contracts/ProposalPayload.sol +++ b/src/contracts/ProposalPayload.sol @@ -1,14 +1,15 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; -import {AaveMisc} from 'aave-address-book/AaveMisc.sol'; +import {MiscEthereum} from 'aave-address-book/MiscEthereum.sol'; import {ProxyAdmin} from 'solidity-utils/contracts/transparent-proxy/ProxyAdmin.sol'; import {TransparentUpgradeableProxy} from 'solidity-utils/contracts/transparent-proxy/TransparentUpgradeableProxy.sol'; import {GenericProposal} from '../libs/GenericProposal.sol'; -import {DistributionTypes} from 'aave-stk-gov-v3/lib/DistributionTypes.sol'; -import {IAaveDistributionManager} from 'aave-stk-gov-v3/interfaces/IAaveDistributionManager.sol'; -import {Vault} from '../interfaces/Actions.sol'; -import {Addresses} from '../libs/Addresses.sol'; +import {DistributionTypes} from 'stake-token/contracts/lib/DistributionTypes.sol'; +import {IAaveDistributionManager} from 'stake-token/contracts/IAaveDistributionManager.sol'; +import {IAggregatedStakeToken} from 'stake-token/contracts/IAggregatedStakeToken.sol'; +import {AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; +import {AaveSafetyModule} from 'aave-address-book/AaveSafetyModule.sol'; /** * @title StkABPTV2 Proposal @@ -16,67 +17,63 @@ import {Addresses} from '../libs/Addresses.sol'; * @notice migrates emissions to a new stkABPT */ contract ProposalPayload { - address public constant AAVE = Addresses.AAVE; - address public constant WSTETH = Addresses.WSTETH; - address public constant STK_ABPT_V1 = Addresses.STK_ABPT_V1; - Vault public constant VAULT = Vault(Addresses.BALANCER_VAULT); + uint256 public constant EMISSION_PER_SECOND = 4456018518518518; // same as current address public immutable STK_ABPT_V1_IMPL; - address public immutable STK_ABPT_V2_IMPL; address public immutable STK_ABPT_V2_PROXY; - constructor(address newStkABPTV1Impl, address newStkABPTV2Impl, address stkABPTV2Proxy) { + constructor(address newStkABPTV1Impl, address stkABPTV2Proxy) { STK_ABPT_V1_IMPL = newStkABPTV1Impl; - STK_ABPT_V2_IMPL = newStkABPTV2Impl; STK_ABPT_V2_PROXY = stkABPTV2Proxy; } function execute() external { - // 1. stop emission on module v1 + // 1. disable cooldown by upgrading the impl + ProxyAdmin(MiscEthereum.PROXY_ADMIN).upgradeAndCall( + TransparentUpgradeableProxy(payable(AaveSafetyModule.STK_ABPT)), + STK_ABPT_V1_IMPL, + abi.encodeWithSignature('initialize()') + ); + + // 2. stop emission on module v1 DistributionTypes.AssetConfigInput[] memory disableConfigs = new DistributionTypes.AssetConfigInput[](1); disableConfigs[0] = DistributionTypes.AssetConfigInput({ emissionPerSecond: 0, totalStaked: 0, // it's overwritten internally - underlyingAsset: STK_ABPT_V1 + underlyingAsset: AaveSafetyModule.STK_ABPT }); - IAaveDistributionManager(STK_ABPT_V1).configureAssets(disableConfigs); - - // 2. disable cooldown by upgrading the impl - ProxyAdmin(AaveMisc.PROXY_ADMIN_ETHEREUM).upgradeAndCall( - TransparentUpgradeableProxy(payable(STK_ABPT_V1)), - STK_ABPT_V1_IMPL, - abi.encodeWithSignature('initialize()') + IAaveDistributionManager(AaveSafetyModule.STK_ABPT).configureAssets(disableConfigs); + MiscEthereum.AAVE_ECOSYSTEM_RESERVE_CONTROLLER.approve( + MiscEthereum.ECOSYSTEM_RESERVE, + AaveV3EthereumAssets.AAVE_UNDERLYING, + AaveSafetyModule.STK_ABPT, + 0 ); - - // 3. create new SM - ProxyAdmin(AaveMisc.PROXY_ADMIN_ETHEREUM).upgradeAndCall( - TransparentUpgradeableProxy(payable(STK_ABPT_V2_PROXY)), - STK_ABPT_V2_IMPL, - abi.encodeWithSignature( - 'initialize(address,address,address,uint256,uint256)', - GenericProposal.SLASHING_ADMIN, - GenericProposal.COOLDOWN_ADMIN, - GenericProposal.CLAIM_HELPER, - GenericProposal.MAX_SLASHING, - GenericProposal.COOLDOWN_SECONDS - ) + MiscEthereum.AAVE_ECOSYSTEM_RESERVE_CONTROLLER.approve( + MiscEthereum.ECOSYSTEM_RESERVE, + AaveV3EthereumAssets.AAVE_UNDERLYING, + AaveSafetyModule.STK_ABPT, + 15_000 ether // rough estimation on total claimable + emission until execution ); - // 4. start emission on module v2 - AaveMisc.AAVE_ECOSYSTEM_RESERVE_CONTROLLER.approve( - AaveMisc.ECOSYSTEM_RESERVE, - AAVE, - STK_ABPT_V2_PROXY, - 180_000 ether // TODO: what is the correct value here? + // 3. start emission on module v2 + IAggregatedStakeToken(STK_ABPT_V2_PROXY).setDistributionEnd( + block.timestamp + GenericProposal.DISTRIBUTION_DURATION ); DistributionTypes.AssetConfigInput[] memory enableConfigs = new DistributionTypes.AssetConfigInput[](1); enableConfigs[0] = DistributionTypes.AssetConfigInput({ - emissionPerSecond: 6365740740740741, // same as current + emissionPerSecond: uint128(EMISSION_PER_SECOND), totalStaked: 0, // it's overwritten internally underlyingAsset: STK_ABPT_V2_PROXY }); IAaveDistributionManager(STK_ABPT_V2_PROXY).configureAssets(enableConfigs); + MiscEthereum.AAVE_ECOSYSTEM_RESERVE_CONTROLLER.approve( + MiscEthereum.ECOSYSTEM_RESERVE, + AaveV3EthereumAssets.AAVE_UNDERLYING, + STK_ABPT_V2_PROXY, + EMISSION_PER_SECOND * GenericProposal.DISTRIBUTION_DURATION + ); } } diff --git a/src/contracts/StkABPTMigrator.sol b/src/contracts/StkABPTMigrator.sol index 8d3dba6..06e4805 100644 --- a/src/contracts/StkABPTMigrator.sol +++ b/src/contracts/StkABPTMigrator.sol @@ -1,16 +1,17 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; -import {AaveGovernanceV2} from 'aave-address-book/AaveGovernanceV2.sol'; +import {GovernanceV3Ethereum} from 'aave-address-book/GovernanceV3Ethereum.sol'; import {AaveV3Ethereum} from 'aave-address-book/AaveV3Ethereum.sol'; -import {AggregatorInterface} from 'aave-address-book/AaveV3.sol'; import {ILido} from '../interfaces/ILido.sol'; import {IWeth} from '../interfaces/IWeth.sol'; import {IWstETH} from '../interfaces/IWstETH.sol'; import {Vault, BPool, BalancerPool, ERC20} from '../interfaces/Actions.sol'; import {Addresses} from '../libs/Addresses.sol'; -import {AggregatedStakedTokenV3} from 'aave-stk-gov-v3/interfaces/AggregatedStakedTokenV3.sol'; +import {IAggregatedStakeToken} from 'stake-token/contracts/IAggregatedStakeToken.sol'; import {Rescuable} from 'solidity-utils/contracts/utils/Rescuable.sol'; +import {AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; +import {AaveSafetyModule} from 'aave-address-book/AaveSafetyModule.sol'; /** * @title StkABPTMigrator @@ -22,11 +23,19 @@ contract StkABPTMigrator is Rescuable { constructor(address stkABPTV2) { // infinite approval for putting aave into the lp - _safeApprove(ERC20(Addresses.AAVE), Addresses.BALANCER_VAULT, type(uint256).max); + _safeApprove( + ERC20(AaveV3EthereumAssets.AAVE_UNDERLYING), + Addresses.BALANCER_VAULT, + type(uint256).max + ); // infinite approval for wrapping stETH - _safeApprove(ERC20(Addresses.STETH), Addresses.WSTETH, type(uint256).max); - // infinite approval for pussing wstETH into the lp - _safeApprove(ERC20(Addresses.WSTETH), Addresses.BALANCER_VAULT, type(uint256).max); + _safeApprove(ERC20(Addresses.STETH), AaveV3EthereumAssets.wstETH_UNDERLYING, type(uint256).max); + // infinite approval for depositing wstETH into the LP + _safeApprove( + ERC20(AaveV3EthereumAssets.wstETH_UNDERLYING), + Addresses.BALANCER_VAULT, + type(uint256).max + ); // infinite approval for putting the lp into stkLP _safeApprove(ERC20(Addresses.ABPT_V2), stkABPTV2, type(uint256).max); STK_ABPT_V2 = stkABPTV2; @@ -41,7 +50,7 @@ contract StkABPTMigrator is Rescuable { * allow the short executor to rescue tokens */ function whoCanRescue() public pure override returns (address) { - return AaveGovernanceV2.SHORT_EXECUTOR; + return GovernanceV3Ethereum.EXECUTOR_LVL_1; } /** @@ -49,15 +58,13 @@ contract StkABPTMigrator is Rescuable { * @param amount the amount of stkABPT to migrate * @param tokenOutAmountsMin the minimum amount of AAVE/WETH you want to receive for redemption * @param poolOutAmountMin the minimum amount of stkABPTV2 tokens you want to receive - * @param all if true, will migrate all AAVE/WETH. If false will migrate proportionally and send the leftovers to your address. */ function migrateStkABPT( uint256 amount, uint256[] calldata tokenOutAmountsMin, - uint256 poolOutAmountMin, - bool all + uint256 poolOutAmountMin ) external { - _migrate(amount, tokenOutAmountsMin, poolOutAmountMin, all); + _migrate(amount, tokenOutAmountsMin, poolOutAmountMin); } /** @@ -65,7 +72,6 @@ contract StkABPTMigrator is Rescuable { * @param amount the amount of stkABPT to migrate * @param tokenOutAmountsMin the minimum amount of AAVE/WETH you want to receive for redemption * @param poolOutAmountMin the minimum amount of stkABPTV2 tokens you want to receive - * @param all if true, will migrate all AAVE/WETH. If false will migrate proportionally and send the leftovers to your address. */ function migrateStkABPTWithPermit( uint256 amount, @@ -74,144 +80,54 @@ contract StkABPTMigrator is Rescuable { bytes32 r, bytes32 s, uint256[] calldata tokenOutAmountsMin, - uint256 poolOutAmountMin, - bool all + uint256 poolOutAmountMin ) external { - AggregatedStakedTokenV3(Addresses.STK_ABPT_V1).permit( - msg.sender, - address(this), - amount, - deadline, - v, - r, - s - ); - _migrate(amount, tokenOutAmountsMin, poolOutAmountMin, all); + try + IAggregatedStakeToken(AaveSafetyModule.STK_ABPT).permit( + msg.sender, + address(this), + amount, + deadline, + v, + r, + s + ) + {} catch {} + _migrate(amount, tokenOutAmountsMin, poolOutAmountMin); } function _migrate( uint256 amount, uint256[] calldata tokenOutAmountsMin, - uint256 poolOutAmountMin, - bool all - ) internal { - AggregatedStakedTokenV3(Addresses.STK_ABPT_V1).transferFrom(msg.sender, address(this), amount); - AggregatedStakedTokenV3(Addresses.STK_ABPT_V1).redeem(address(this), amount); - if (all) { - _migrateAll( - BPool(Addresses.ABPT_V1).balanceOf(address(this)), - tokenOutAmountsMin, - poolOutAmountMin - ); - } else { - _migrateProportionally( - BPool(Addresses.ABPT_V1).balanceOf(address(this)), - tokenOutAmountsMin, - poolOutAmountMin - ); - } - AggregatedStakedTokenV3(STK_ABPT_V2).stake( - msg.sender, - BalancerPool(Addresses.ABPT_V2).balanceOf(address(this)) - ); - } - - function _migrateProportionally( - uint256 poolInAmount, - uint256[] calldata tokenOutAmountsMin, uint256 poolOutAmountMin ) internal { - // Exit v1 pool - uint256 wethBalanceBefore = ERC20(Addresses.WETH).balanceOf(address(this)); - uint256 aaveBalanceBefore = ERC20(Addresses.AAVE).balanceOf(address(this)); - uint256 wstETHBalanceBefore = ERC20(Addresses.WSTETH).balanceOf(address(this)); - - BPool(Addresses.ABPT_V1).exitPool(poolInAmount, tokenOutAmountsMin); - uint256 wethBalanceAfter = ERC20(Addresses.WETH).balanceOf(address(this)); - uint256 aaveBalanceAfter = ERC20(Addresses.AAVE).balanceOf(address(this)); - - (address[] memory outTokens, uint256[] memory balances, ) = Vault(Addresses.BALANCER_VAULT) - .getPoolTokens(Addresses.ABPT_V2_ID); - // migrate weth to wstETH - require(outTokens[0] == Addresses.WSTETH); - require(outTokens[1] == Addresses.AAVE); - uint256[] memory tokenInAmounts = new uint256[](outTokens.length); - tokenInAmounts[0] = _wethToWesth(wethBalanceAfter - wethBalanceBefore); - tokenInAmounts[1] = aaveBalanceAfter - aaveBalanceBefore; - // Calculate amounts for even join - // 1) find the lowest UserBalance-to-PoolBalance ratio - // 2) multiply by this ratio to get in amounts - uint256 lowestRatio = type(uint256).max; - uint256 lowestRatioToken = 0; - - for (uint256 i = 0; i < outTokens.length; ++i) { - uint256 ratio = (1 ether * tokenInAmounts[i]) / balances[i]; - if (ratio < lowestRatio) { - lowestRatio = ratio; - lowestRatioToken = i; - } - } - for (uint256 i = 0; i < outTokens.length; ++i) { - // Keep original amount for "bottleneck" token to avoid dust - if (lowestRatioToken == i) { - balances[i] = tokenInAmounts[i]; - } else { - balances[i] = (balances[i] * lowestRatio) / 1 ether; - } - } - // Join v2 pool and transfer v2 BPTs to user - bytes memory userData = abi.encode( - BalancerPool.JoinKind.EXACT_TOKENS_IN_FOR_BPT_OUT, - balances, - poolOutAmountMin + // snapshot balances + uint256 wethBalanceBefore = ERC20(AaveV3EthereumAssets.WETH_UNDERLYING).balanceOf( + address(this) ); - Vault.JoinPoolRequest memory request = Vault.JoinPoolRequest( - outTokens, - balances, - userData, - false + uint256 aaveBalanceBefore = ERC20(AaveV3EthereumAssets.AAVE_UNDERLYING).balanceOf( + address(this) ); - Vault(Addresses.BALANCER_VAULT).joinPool( - Addresses.ABPT_V2_ID, - address(this), + uint256 abptV1BalanceBefore = ERC20(Addresses.ABPT_V1).balanceOf(address(this)); + uint256 abptV2BalanceBefore = ERC20(Addresses.ABPT_V2).balanceOf(address(this)); + // Exit v1 pool + IAggregatedStakeToken(AaveSafetyModule.STK_ABPT).transferFrom( + msg.sender, address(this), - request + amount ); - // Send "change" back - uint256 finalAaveBalance = ERC20(Addresses.AAVE).balanceOf(address(this)); - if (finalAaveBalance > aaveBalanceBefore) { - require( - ERC20(Addresses.AAVE).transfer(msg.sender, finalAaveBalance - aaveBalanceBefore), - 'ERR_TRANSFER_FAILED' - ); - } - uint256 finalWstETHBalance = ERC20(Addresses.WSTETH).balanceOf(address(this)); - if (finalWstETHBalance > wstETHBalanceBefore) { - require( - ERC20(Addresses.WSTETH).transfer(msg.sender, finalWstETHBalance - wstETHBalanceBefore), - 'ERR_TRANSFER_FAILED' - ); - } - } - - function _migrateAll( - uint256 poolInAmount, - uint256[] calldata tokenOutAmountsMin, - uint256 poolOutAmountMin - ) internal { - // Exit v1 pool - uint256 wethBalanceBefore = ERC20(Addresses.WETH).balanceOf(address(this)); - uint256 aaveBalanceBefore = ERC20(Addresses.AAVE).balanceOf(address(this)); + IAggregatedStakeToken(AaveSafetyModule.STK_ABPT).redeem(address(this), amount); + uint256 poolInAmount = BPool(Addresses.ABPT_V1).balanceOf(address(this)) - abptV1BalanceBefore; BPool(Addresses.ABPT_V1).exitPool(poolInAmount, tokenOutAmountsMin); - uint256 wethBalanceAfter = ERC20(Addresses.WETH).balanceOf(address(this)); - uint256 aaveBalanceAfter = ERC20(Addresses.AAVE).balanceOf(address(this)); + uint256 wethBalanceAfter = ERC20(AaveV3EthereumAssets.WETH_UNDERLYING).balanceOf(address(this)); + uint256 aaveBalanceAfter = ERC20(AaveV3EthereumAssets.AAVE_UNDERLYING).balanceOf(address(this)); // Join v2 pool and transfer v2 BPTs to user address[] memory outTokens = new address[](2); - outTokens[0] = Addresses.WSTETH; - outTokens[1] = Addresses.AAVE; + outTokens[0] = AaveV3EthereumAssets.wstETH_UNDERLYING; + outTokens[1] = AaveV3EthereumAssets.AAVE_UNDERLYING; uint256[] memory tokenInAmounts = new uint[](outTokens.length); tokenInAmounts[0] = _wethToWesth(wethBalanceAfter - wethBalanceBefore); tokenInAmounts[1] = aaveBalanceAfter - aaveBalanceBefore; @@ -233,21 +149,24 @@ contract StkABPTMigrator is Rescuable { address(this), request ); + + IAggregatedStakeToken(STK_ABPT_V2).stake( + msg.sender, + BalancerPool(Addresses.ABPT_V2).balanceOf(address(this)) - abptV2BalanceBefore + ); } function _wethToWesth(uint256 amount) internal returns (uint256) { // Unwrap WETH to ETH - IWeth(Addresses.WETH).withdraw(amount); + IWeth(AaveV3EthereumAssets.WETH_UNDERLYING).withdraw(amount); // supply ETH to stETH uint256 stETHBefore = ERC20(Addresses.STETH).balanceOf(address(this)); - ILido(Addresses.STETH).submit{value: amount}(address(0)); + ILido(Addresses.STETH).submit{value: amount}(GovernanceV3Ethereum.EXECUTOR_LVL_1); uint256 stETHAfter = ERC20(Addresses.STETH).balanceOf(address(this)); // wrap stETH to wstETH - return IWstETH(Addresses.WSTETH).wrap(stETHAfter - stETHBefore); + return IWstETH(AaveV3EthereumAssets.wstETH_UNDERLYING).wrap(stETHAfter - stETHBefore); } - // --- Internals ---Fréland, 68240, Franceranrupt - function _safeApprove(ERC20 token, address spender, uint256 amount) internal { if (token.allowance(address(this), spender) > 0) { token.approve(spender, 0); diff --git a/src/libs/Addresses.sol b/src/libs/Addresses.sol index dd40cf1..6b5801a 100644 --- a/src/libs/Addresses.sol +++ b/src/libs/Addresses.sol @@ -2,20 +2,11 @@ pragma solidity >=0.6.12; library Addresses { - address internal constant AAVE = 0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9; - address internal constant AAVE_ORACLE = 0x547a514d5e3769680Ce22B2361c10Ea13619e8a9; - - address internal constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; - - address internal constant WSTETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; - address internal constant WSTETH_ORACLE = 0xA9F30e6ED4098e9439B2ac8aEA2d3fc26BcEbb45; - address internal constant STETH = 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84; address internal constant BALANCER_VAULT = 0xBA12222222228d8Ba445958a75a0704d566BF2C8; address internal constant ABPT_V1 = 0x41A08648C3766F9F9d85598fF102a08f4ef84F84; address internal constant ABPT_V1_BPOOL = 0xC697051d1C6296C24aE3bceF39acA743861D9A81; - address internal constant STK_ABPT_V1 = 0xa1116930326D21fB917d5A27F1E9943A9595fb47; address internal constant ABPT_V2 = 0x3de27EFa2F1AA663Ae5D458857e731c129069F29; bytes32 internal constant ABPT_V2_ID = diff --git a/src/libs/GenericProposal.sol b/src/libs/GenericProposal.sol index 51a0b9e..aa6a6c7 100644 --- a/src/libs/GenericProposal.sol +++ b/src/libs/GenericProposal.sol @@ -1,19 +1,19 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {AaveMisc} from 'aave-address-book/AaveMisc.sol'; -import {AaveGovernanceV2} from 'aave-address-book/AaveGovernanceV2.sol'; +import {MiscEthereum} from 'aave-address-book/MiscEthereum.sol'; +import {GovernanceV3Ethereum} from 'aave-address-book/GovernanceV3Ethereum.sol'; library GenericProposal { - address public constant SLASHING_ADMIN = AaveGovernanceV2.SHORT_EXECUTOR; + address public constant SLASHING_ADMIN = GovernanceV3Ethereum.EXECUTOR_LVL_1; - address public constant COOLDOWN_ADMIN = AaveGovernanceV2.SHORT_EXECUTOR; + address public constant COOLDOWN_ADMIN = GovernanceV3Ethereum.EXECUTOR_LVL_1; - address public constant CLAIM_HELPER = AaveGovernanceV2.SHORT_EXECUTOR; + address public constant CLAIM_HELPER = GovernanceV3Ethereum.EXECUTOR_LVL_1; - address public constant REWARDS_VAULT = AaveMisc.ECOSYSTEM_RESERVE; + address public constant REWARDS_VAULT = MiscEthereum.ECOSYSTEM_RESERVE; - address public constant EMISSION_MANAGER = AaveGovernanceV2.SHORT_EXECUTOR; + address public constant EMISSION_MANAGER = GovernanceV3Ethereum.EXECUTOR_LVL_1; uint256 public constant MAX_SLASHING = 3000; // 30% @@ -21,5 +21,5 @@ library GenericProposal { uint256 public constant UNSTAKE_WINDOW = 172800; // 2 days - uint128 public constant DISTRIBUTION_DURATION = 3155692600; // 100 years + uint128 public constant DISTRIBUTION_DURATION = 365 days; // 1 year } diff --git a/tests/E2E.t.sol b/tests/E2E.t.sol index df2df0e..ed59890 100644 --- a/tests/E2E.t.sol +++ b/tests/E2E.t.sol @@ -2,10 +2,10 @@ pragma solidity ^0.8.0; import 'forge-std/Test.sol'; -import {GovHelpers} from 'aave-helpers/GovHelpers.sol'; -import {AaveGovernanceV2} from 'aave-address-book/AaveGovernanceV2.sol'; +import {GovV3Helpers} from 'aave-helpers/GovV3Helpers.sol'; import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; -import {AggregatedStakedTokenV3} from 'aave-stk-gov-v3/interfaces/AggregatedStakedTokenV3.sol'; +import {IAggregatedStakeToken} from 'stake-token/contracts/IAggregatedStakeToken.sol'; +import {AggregatedStakedTokenV3} from 'stk-no-cooldown/interfaces/AggregatedStakedTokenV3.sol'; import {IERC20} from 'openzeppelin-contracts/contracts/token/ERC20/IERC20.sol'; import {DeployOracles} from '../scripts/00_PriceOracles.s.sol'; import {DeployImpl} from '../scripts/01_DeployStkAbptV2Impl.sol'; @@ -14,11 +14,12 @@ import {StkABPTMigrator} from '../src/contracts/StkABPTMigrator.sol'; import {SigUtils} from './SigUtils.sol'; import {BalancerSharedPoolPriceProvider, BPool} from '../src/contracts/BalancerSharedPoolPriceProvider.sol'; import {BalancerV2SharedPoolPriceProvider, BVaultV2, BPoolV2} from '../src/contracts/BalancerV2SharedPoolPriceProvider.sol'; -import {Addresses} from '../src/libs/Addresses.sol'; +import {AaveSafetyModule} from 'aave-address-book/AaveSafetyModule.sol'; +import {AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; contract E2E is Test { address constant STK_ABPT_WHALE = 0xF23c8539069C471F5C12692a3471C9F4E8B88BC2; - address public constant STK_ABPT_V1 = 0xa1116930326D21fB917d5A27F1E9943A9595fb47; + address public constant STK_ABPT_V1 = AaveSafetyModule.STK_ABPT; address public stkAbptV1Impl; address public stkAbptV2Impl; @@ -35,25 +36,25 @@ contract E2E is Test { * ETH: ~2006 $ * AAVE: ~80.42 $ */ - vm.createSelectFork(vm.rpcUrl('mainnet'), 17711546); - DeployOracles step0 = new DeployOracles(); - (address oracle1, address oracle2) = step0._deploy(); - abptOracle = BalancerSharedPoolPriceProvider(oracle1); - abptv2Oracle = BalancerV2SharedPoolPriceProvider(oracle2); + vm.createSelectFork(vm.rpcUrl('mainnet'), 19026444); + // DeployOracles step0 = new DeployOracles(); + // (address oracle1, address oracle2) = step0._deploy(); + abptOracle = BalancerSharedPoolPriceProvider(0x209Ad99bd808221293d03827B86cC544bcA0023b); + abptv2Oracle = BalancerV2SharedPoolPriceProvider(0xADf86b537eF08591c2777E144322E8b0Ca7E82a7); // deploy impls DeployImpl step1 = new DeployImpl(); - (stkAbptV1Impl, stkAbptV2Impl, stkABPTV2) = step1._deploy(); + (stkAbptV1Impl, stkABPTV2) = step1._deploy(); // deploy actual payload DeployPayload step2 = new DeployPayload(); - address payload = step2._deploy(stkAbptV1Impl, stkAbptV2Impl, stkABPTV2); + address payload = step2._deploy(stkAbptV1Impl, stkABPTV2); // deploy migration helper migrator = new StkABPTMigrator(stkABPTV2); // execute proposal - GovHelpers.executePayload(vm, payload, AaveGovernanceV2.SHORT_EXECUTOR); + GovV3Helpers.executePayload(vm, payload); // create test user ownerPrivateKey = 0xA11CE; @@ -66,7 +67,6 @@ contract E2E is Test { AggregatedStakedTokenV3(STK_ABPT_V1).balanceOf(STK_ABPT_WHALE) ); vm.stopPrank(); - vm.startPrank(owner); } /** @@ -74,6 +74,7 @@ contract E2E is Test { * The new version enters post slashing mode, which means ppl should be able to withdraw without a cooldown. */ function test_redeem() public { + vm.startPrank(owner); address abpt = AggregatedStakedTokenV3(STK_ABPT_V1).STAKED_TOKEN(); uint256 stkAbptBalanceBefore = IERC20(STK_ABPT_V1).balanceOf(owner); uint256 abptBalanceBefore = IERC20(abpt).balanceOf(owner); @@ -87,46 +88,56 @@ contract E2E is Test { * @dev Migrate stkAbpt -> stkAbpt v2 via BActions */ function test_migrateStkAbpt() public { + vm.startPrank(owner); uint256 amount = IERC20(STK_ABPT_V1).balanceOf(owner); IERC20(STK_ABPT_V1).approve(address(migrator), type(uint256).max); uint[] memory tokenOutAmountsMin = new uint[](2); // calculate minOut based on $ value - 0.01 % // this should happen offchain - uint256 minBptOut = ((amount * uint256(abptOracle.latestAnswer())) / + uint256 expectedBptOut = ((amount * uint256(abptOracle.latestAnswer())) / uint256(abptv2Oracle.latestAnswer())); - uint256 minBptOutWithSlippage = (minBptOut * 9_999) / 10_000; + uint256 minBptOutWithSlippage = (expectedBptOut * 9_999) / 10_000; - migrator.migrateStkABPT(amount, tokenOutAmountsMin, minBptOutWithSlippage, true); + migrator.migrateStkABPT(amount, tokenOutAmountsMin, minBptOutWithSlippage); uint256 actualBPT = IERC20(stkABPTV2).balanceOf(owner); - assertGe(actualBPT, minBptOutWithSlippage); - assertLt(actualBPT - minBptOutWithSlippage, minBptOut - minBptOutWithSlippage); + assertGe(actualBPT, minBptOutWithSlippage, 'RECEIVED_LESS_THEN_MIN'); } - /** - * @dev Migrate partial stkAbpt -> stkAbpt v2 via BActions - * In this case you would need to control slippage - */ - function test_migratePartialStkAbpt() public { - IERC20(STK_ABPT_V1).approve(address(migrator), type(uint256).max); - uint[] memory tokenOutAmountsMin = new uint[](2); - migrator.migrateStkABPT(IERC20(STK_ABPT_V1).balanceOf(owner), tokenOutAmountsMin, 0, false); - uint256 wstETHBalance = IERC20(Addresses.WSTETH).balanceOf(owner); - uint256 aaveBalance = IERC20(Addresses.AAVE).balanceOf(owner); - if (wstETHBalance == 0) { - assertGt(aaveBalance, 0); - } else { - assertGt(wstETHBalance, 0); + function test_migrateStkAbpt_currentHolders() public { + address[] memory users = new address[](5); + users[0] = 0x7BfeA1979e58AA73beB34D4577272B5Ba16479fD; + users[1] = 0x9bec07CB8E702FA848Cda6A958453455053a016e; + users[2] = 0x28a55C4b4f9615FDE3CDAdDf6cc01FcF2E38A6b0; + users[3] = 0x741AA7CFB2c7bF2A1E7D4dA2e3Df6a56cA4131F3; + users[4] = 0xe705b1D26B85c9F9f91A3690079D336295F14F08; + + for (uint256 i = 0; i < users.length; i++) { + vm.startPrank(users[i]); + uint256 amount = IERC20(STK_ABPT_V1).balanceOf(users[i]); + IERC20(STK_ABPT_V1).approve(address(migrator), type(uint256).max); + uint[] memory tokenOutAmountsMin = new uint[](2); + + // calculate minOut based on $ value - 0.01 % + // this should happen offchain + uint256 expectedBptOut = ((amount * uint256(abptOracle.latestAnswer())) / + uint256(abptv2Oracle.latestAnswer())); + uint256 minBptOutWithSlippage = (expectedBptOut * 9_999) / 10_000; + + migrator.migrateStkABPT(amount, tokenOutAmountsMin, minBptOutWithSlippage); + + uint256 actualBPT = IERC20(stkABPTV2).balanceOf(users[i]); + assertGe(actualBPT, minBptOutWithSlippage, 'RECEIVED_LESS_THEN_MIN'); } - assertEq(IERC20(stkABPTV2).balanceOf(owner), 228733940221947756582513); } function test_migrationWithPermit() public { + vm.startPrank(owner); SigUtils.Permit memory permit = SigUtils.Permit({ owner: owner, spender: address(migrator), - value: IERC20(STK_ABPT_V1).balanceOf(owner), + value: AggregatedStakedTokenV3(STK_ABPT_V1).balanceOf(owner), nonce: AggregatedStakedTokenV3(STK_ABPT_V1)._nonces(owner), deadline: block.timestamp + 1 days }); @@ -145,17 +156,16 @@ contract E2E is Test { r, s, tokenOutAmountsMin, - 0, - true + 0 ); } function test_claimRewards() public { test_migrateStkAbpt(); vm.warp(block.timestamp + 10000); - uint256 rewards = AggregatedStakedTokenV3(stkABPTV2).getTotalRewardsBalance(owner); + uint256 rewards = IAggregatedStakeToken(stkABPTV2).getTotalRewardsBalance(owner); assertGt(rewards, 0); - AggregatedStakedTokenV3(stkABPTV2).claimRewards(owner, type(uint256).max); + IAggregatedStakeToken(stkABPTV2).claimRewards(owner, type(uint256).max); } } @@ -172,7 +182,7 @@ contract OracleTest is Test { } function testAAVEPrice() public { - console.log(AaveV3Ethereum.ORACLE.getAssetPrice(Addresses.AAVE)); + console.log(AaveV3Ethereum.ORACLE.getAssetPrice(AaveV3EthereumAssets.AAVE_UNDERLYING)); } function testV1OraclePrice() public {