diff --git a/diffs/AaveV2Ethereum_AAMPLSecondDistribution_20240429_before_AaveV2Ethereum_AAMPLSecondDistribution_20240429_after.md b/diffs/AaveV2Ethereum_AAMPLSecondDistribution_20240429_before_AaveV2Ethereum_AAMPLSecondDistribution_20240429_after.md new file mode 100644 index 000000000..c15d3e2bc --- /dev/null +++ b/diffs/AaveV2Ethereum_AAMPLSecondDistribution_20240429_before_AaveV2Ethereum_AAMPLSecondDistribution_20240429_after.md @@ -0,0 +1,5 @@ +## Raw diff + +```json +{} +``` \ No newline at end of file diff --git a/src/20240429_AaveV2Ethereum_AAMPLSecondDistribution/AAMPLSecondDistribution.md b/src/20240429_AaveV2Ethereum_AAMPLSecondDistribution/AAMPLSecondDistribution.md new file mode 100644 index 000000000..a754e54ba --- /dev/null +++ b/src/20240429_AaveV2Ethereum_AAMPLSecondDistribution/AAMPLSecondDistribution.md @@ -0,0 +1,57 @@ +--- +title: "aAMPL Second Distribution" +author: "BGD Labs (@bgdlabs)" +discussions: "https://governance.aave.com/t/arfc-aampl-second-distribution/17464" +snapshot: "https://snapshot.org/#/aave.eth/proposal/0x372ea60168390ca30be8890ae18ba3c1bb171428ad613a3c8c1a568721c1d65d" +--- + +## Simple Summary + +A proposal for a follow-up distribution of 766'436 aUSDC (762'604 aUSDC + 3'832 aUSDC angle labs fee) from the Aave Collector to allow full withdrawals by aAMPL suppliers to Aave v2 Ethereum, consequence of the problem detected at the end of 2023. + +## Motivation + +In December 2023, a problem was detected on the AMPL custom reserve on Aave v2 Ethereum, causing an unexpected inflation of AMPL-related balances and supply, not following the intended design by the Ampleforth team. + +After further analysis and remediation strategy, the Aave governance approved and executed a proposal on April 17th to provide approximately 300’000 USDC for aAMPL holders to claim. This was designed as an initial and interim distribution, with the sole objective of providing liquidity for users affected as soon as possible. Meanwhile, the service providers of the Aave DAO and the Ampleforth team completed further analysis for a final distribution proposal; this one. + +## Specification + +To fully understand the problem at hand and the rationale of this distribution, we recommend mandatorily reading its specification on the [forum](https://governance.aave.com/t/ampl-problem-on-aave-v2-ethereum/15886/155), to make an informed decision. + +In summary, this proposal recommends making claimable 766'436 aUSDC (762'604 aUSDC + 3'832 aUSDC angle labs fee) from the Aave Ethereum Collector, in addition to the already distributed 300’000 USDC, making a total of approximately $1,066,436. + +Some key points output of the analysis are: + +- Decisions when doing the analysis have been made to favor aAMPL holders, whenever it was subjective generally, but always trying to maintain objectivity on the expected dynamics of AMPL on Aave. +- Real returns of supplied AMPL on Aave have been recalculated from the freezing period until the expected execution time of the proposal (beginning of May 2024). This approach, compared to calculating from market inception, preserves four times more AMPL for users, maximizing welfare for AMPL suppliers. +- The previous analysis resulted in identifying a claimable amount of 351,579 AMPL as of December 16th, 2023, after rectifying highly evident artificial inflation within the 764,303 aAMPL total supply. As a reference point, debt levels were observed to be at 42,847 AMPL during that period. +- As further compensation for the time passed since December during which funds were not withdrawable, the latest on-chain rate is applicable on the 351’579 AMPL: 256% compounding for more than 4 months, which results in a total of 882’869 AMPL, or $1,057,677. + Uniform 100% utilization from 16th December 2023 to the beginning of May 2024 is considered, disregarding the underlying rebases of AMPL. This also tries to heavily increase the amount received by aAMPL holders, as once again, debt levels are currently just 72K vAMPL, compared to the much greater compensation amount. +- Pricing claims at $1.198 price per AMPL, despite the price falling below and negative rebases accruing on the aAMPL supply since. + Any holding by the Aave Collector contract is not included in the compensation, to increase the amount received by other aAMPL holders. +- Interest generated from the 300’000 available on 17th April to withdraw is discounted, as those funds are fully claimable by users. +- The claim for the Mooniswap pool has been proxied with sub-claims for LPs on it. + +The Ampleforth team agreed to compensate 40% of the total after proposal execution, as stated on https://governance.aave.com/t/ampl-problem-on-aave-v2-ethereum/15886/128, which will be done after Aave fully approves this distribution. + +Regarding the technical details, the proposal will: + +- Transfer v3 aUSDC from the collector (766.436k including a 0.5% fee for angle labs) +- Approve the full amount to [0x8BB4C975Ff3c250e0ceEA271728547f3802B36Fd](https://etherscan.io/address/0x8BB4C975Ff3c250e0ceEA271728547f3802B36Fd) which is the distribution creator by Angle Labs +- Create a campaign to distribute funds to the affected users + +2 hours after proposal execution, users will be able to claim the aUSDC on https://app.merkl.xyz/ + +## References + +- Implementation: [AaveV2Ethereum](https://github.com/bgd-labs/aave-proposals-v3/blob/main/src/20240429_AaveV2Ethereum_AAMPLSecondDistribution/AaveV2Ethereum_AAMPLSecondDistribution_20240429.sol) +- Tests: [AaveV2Ethereum](https://github.com/bgd-labs/aave-proposals-v3/blob/main/src/20240429_AaveV2Ethereum_AAMPLSecondDistribution/AaveV2Ethereum_AAMPLSecondDistribution_20240429.t.sol) +- [Snapshot](https://snapshot.org/#/aave.eth/proposal/0x372ea60168390ca30be8890ae18ba3c1bb171428ad613a3c8c1a568721c1d65d) +- [Discussion](https://governance.aave.com/t/arfc-aampl-second-distribution/17464) +- [Distribution:IPFS](https://angle-blog.infura-ipfs.io/ipfs/QmTvv4u6MUb6cwThCi7tma1ZJ1XUe9mQmaGcHEmLZhazre) +- [Distribution:formatted](https://github.com/bgd-labs/aave-proposals-v3/blob/main/src/20240429_AaveV2Ethereum_AAMPLSecondDistribution/distribution.pdf) + +## Copyright + +Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/). diff --git a/src/20240429_AaveV2Ethereum_AAMPLSecondDistribution/AAMPLSecondDistribution_20240429.s.sol b/src/20240429_AaveV2Ethereum_AAMPLSecondDistribution/AAMPLSecondDistribution_20240429.s.sol new file mode 100644 index 000000000..20989aee7 --- /dev/null +++ b/src/20240429_AaveV2Ethereum_AAMPLSecondDistribution/AAMPLSecondDistribution_20240429.s.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {GovV3Helpers, IPayloadsControllerCore, PayloadsControllerUtils} from 'aave-helpers/GovV3Helpers.sol'; +import {EthereumScript} from 'aave-helpers/ScriptUtils.sol'; +import {AaveV2Ethereum_AAMPLSecondDistribution_20240429} from './AaveV2Ethereum_AAMPLSecondDistribution_20240429.sol'; + +/** + * @dev Deploy Ethereum + * deploy-command: make deploy-ledger contract=src/20240429_AaveV2Ethereum_AAMPLSecondDistribution/AAMPLSecondDistribution_20240429.s.sol:DeployEthereum chain=mainnet + * verify-command: npx catapulta-verify -b broadcast/AAMPLSecondDistribution_20240429.s.sol/1/run-latest.json + */ +contract DeployEthereum is EthereumScript { + function run() external broadcast { + // deploy payloads + address payload0 = GovV3Helpers.deployDeterministic( + type(AaveV2Ethereum_AAMPLSecondDistribution_20240429).creationCode + ); + + // compose action + IPayloadsControllerCore.ExecutionAction[] + memory actions = new IPayloadsControllerCore.ExecutionAction[](1); + actions[0] = GovV3Helpers.buildAction(payload0); + + // register action at payloadsController + GovV3Helpers.createPayload(actions); + } +} + +/** + * @dev Create Proposal + * command: make deploy-ledger contract=src/20240429_AaveV2Ethereum_AAMPLSecondDistribution/AAMPLSecondDistribution_20240429.s.sol:CreateProposal chain=mainnet + */ +contract CreateProposal is EthereumScript { + function run() external { + // create payloads + PayloadsControllerUtils.Payload[] memory payloads = new PayloadsControllerUtils.Payload[](1); + + // compose actions for validation + IPayloadsControllerCore.ExecutionAction[] + memory actionsEthereum = new IPayloadsControllerCore.ExecutionAction[](1); + actionsEthereum[0] = GovV3Helpers.buildAction( + type(AaveV2Ethereum_AAMPLSecondDistribution_20240429).creationCode + ); + payloads[0] = GovV3Helpers.buildMainnetPayload(vm, actionsEthereum); + + // create proposal + vm.startBroadcast(); + GovV3Helpers.createProposal( + vm, + payloads, + GovV3Helpers.ipfsHashFile( + vm, + 'src/20240429_AaveV2Ethereum_AAMPLSecondDistribution/AAMPLSecondDistribution.md' + ) + ); + } +} diff --git a/src/20240429_AaveV2Ethereum_AAMPLSecondDistribution/AaveV2Ethereum_AAMPLSecondDistribution_20240429.sol b/src/20240429_AaveV2Ethereum_AAMPLSecondDistribution/AaveV2Ethereum_AAMPLSecondDistribution_20240429.sol new file mode 100644 index 000000000..940e3fd1a --- /dev/null +++ b/src/20240429_AaveV2Ethereum_AAMPLSecondDistribution/AaveV2Ethereum_AAMPLSecondDistribution_20240429.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; +import {IERC20} from 'solidity-utils/contracts/oz-common/interfaces/IERC20.sol'; +import {IProposalGenericExecutor} from 'aave-helpers/interfaces/IProposalGenericExecutor.sol'; +import {IDistributionCreator} from './interfaces/IDistributionCreator.sol'; +import {SafeERC20} from 'solidity-utils/contracts/oz-common/SafeERC20.sol'; + +/** + * @title aAMPL Second Distribution + * @author BGD Labs (@bgdlabs) + * - Snapshot: https://snapshot.org/#/aave.eth/proposal/0x372ea60168390ca30be8890ae18ba3c1bb171428ad613a3c8c1a568721c1d65d + * - Discussion: https://governance.aave.com/t/arfc-aampl-second-distribution/17464 + */ +contract AaveV2Ethereum_AAMPLSecondDistribution_20240429 is IProposalGenericExecutor { + using SafeERC20 for IERC20; + + IDistributionCreator public constant DISTRIBUTION_CREATOR = + IDistributionCreator(0x8BB4C975Ff3c250e0ceEA271728547f3802B36Fd); + address public constant REWARD_TOKEN = AaveV3EthereumAssets.USDC_A_TOKEN; + uint256 public constant REWARD_AMOUNT_PLUS_FEES = 766_436_793678; + string public constant FILE_URL = + 'https://angle-blog.infura-ipfs.io/ipfs/QmTvv4u6MUb6cwThCi7tma1ZJ1XUe9mQmaGcHEmLZhazre'; + + function execute() external { + // 1. send funds to executor (0.5% fee for angle merkle distributor) + AaveV3Ethereum.COLLECTOR.transfer( + AaveV3EthereumAssets.USDC_A_TOKEN, + address(this), + REWARD_AMOUNT_PLUS_FEES + 1 // account for imprecision on transfer + ); + + uint256 aUSDCBalance = IERC20(AaveV3EthereumAssets.USDC_A_TOKEN).balanceOf(address(this)); + + // 2. approve to merkl + IERC20(REWARD_TOKEN).forceApprove(address(DISTRIBUTION_CREATOR), aUSDCBalance); + + // 3. create campaign + IDistributionCreator.CampaignParameters memory newCampaign = IDistributionCreator + .CampaignParameters({ + campaignId: '', + creator: address(0), + rewardToken: REWARD_TOKEN, + amount: aUSDCBalance, + campaignType: 4, + startTimestamp: uint32(block.timestamp + 2 hours), + duration: 1 hours, + campaignData: abi.encode(FILE_URL, string(''), bytes('0x')) + }); + DISTRIBUTION_CREATOR.createCampaign(newCampaign); + } +} diff --git a/src/20240429_AaveV2Ethereum_AAMPLSecondDistribution/AaveV2Ethereum_AAMPLSecondDistribution_20240429.t.sol b/src/20240429_AaveV2Ethereum_AAMPLSecondDistribution/AaveV2Ethereum_AAMPLSecondDistribution_20240429.t.sol new file mode 100644 index 000000000..1444229c9 --- /dev/null +++ b/src/20240429_AaveV2Ethereum_AAMPLSecondDistribution/AaveV2Ethereum_AAMPLSecondDistribution_20240429.t.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {AaveV2Ethereum} from 'aave-address-book/AaveV2Ethereum.sol'; +import {AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; +import {ProtocolV2TestBase} from 'aave-helpers/ProtocolV2TestBase.sol'; +import {AaveV2Ethereum_AAMPLSecondDistribution_20240429, IDistributionCreator} from './AaveV2Ethereum_AAMPLSecondDistribution_20240429.sol'; +import {GovernanceV3Ethereum} from 'aave-address-book/GovernanceV3Ethereum.sol'; + +/** + * @dev Test for AaveV2Ethereum_AAMPLSecondDistribution_20240429 + * command: make test-contract filter=AaveV2Ethereum_AAMPLSecondDistribution_20240429 + */ +contract AaveV2Ethereum_AAMPLSecondDistribution_20240429_Test is ProtocolV2TestBase { + AaveV2Ethereum_AAMPLSecondDistribution_20240429 internal proposal; + + function setUp() public { + vm.createSelectFork(vm.rpcUrl('mainnet'), 19761486); + proposal = new AaveV2Ethereum_AAMPLSecondDistribution_20240429(); + } + + /** + * @dev executes the generic test suite including e2e and config snapshots + */ + function test_defaultProposalExecution() public { + defaultTest( + 'AaveV2Ethereum_AAMPLSecondDistribution_20240429', + AaveV2Ethereum.POOL, + address(proposal) + ); + } + + function test_createCampaign() public { + executePayload(vm, address(proposal)); + + IDistributionCreator.CampaignParameters memory campaign = proposal + .DISTRIBUTION_CREATOR() + .campaign(bytes32(0x04538a26c6088b568f6364ccc59b577ece656177c84076ab281dba3ea91576c7)); + + assertEq(campaign.creator, GovernanceV3Ethereum.EXECUTOR_LVL_1); + assertEq(campaign.rewardToken, proposal.REWARD_TOKEN()); + assertLt(campaign.amount, proposal.REWARD_AMOUNT_PLUS_FEES() + 1); + assertEq(campaign.campaignType, 4); + assertGt(campaign.startTimestamp, block.timestamp); + assertEq(campaign.duration, 3600); + (string memory url, , ) = abi.decode(campaign.campaignData, (string, string, bytes)); + assertEq(url, proposal.FILE_URL()); + } +} diff --git a/src/20240429_AaveV2Ethereum_AAMPLSecondDistribution/config.ts b/src/20240429_AaveV2Ethereum_AAMPLSecondDistribution/config.ts new file mode 100644 index 000000000..2d852f682 --- /dev/null +++ b/src/20240429_AaveV2Ethereum_AAMPLSecondDistribution/config.ts @@ -0,0 +1,14 @@ +import {ConfigFile} from '../../generator/types'; +export const config: ConfigFile = { + rootOptions: { + pools: ['AaveV2Ethereum'], + title: 'aAMPL Second Distribution', + shortName: 'AAMPLSecondDistribution', + date: '20240429', + author: 'BGD Labs (@bgdlabs)', + discussion: 'https://governance.aave.com/t/arfc-aampl-second-distribution/17464', + snapshot: + 'https://snapshot.org/#/aave.eth/proposal/0x372ea60168390ca30be8890ae18ba3c1bb171428ad613a3c8c1a568721c1d65d', + }, + poolOptions: {AaveV2Ethereum: {configs: {}, cache: {blockNumber: 19760445}}}, +}; diff --git a/src/20240429_AaveV2Ethereum_AAMPLSecondDistribution/distribution.pdf b/src/20240429_AaveV2Ethereum_AAMPLSecondDistribution/distribution.pdf new file mode 100644 index 000000000..68e2794ca Binary files /dev/null and b/src/20240429_AaveV2Ethereum_AAMPLSecondDistribution/distribution.pdf differ diff --git a/src/20240429_AaveV2Ethereum_AAMPLSecondDistribution/interfaces/IDistributionCreator.sol b/src/20240429_AaveV2Ethereum_AAMPLSecondDistribution/interfaces/IDistributionCreator.sol new file mode 100644 index 000000000..9661d3d47 --- /dev/null +++ b/src/20240429_AaveV2Ethereum_AAMPLSecondDistribution/interfaces/IDistributionCreator.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IDistributionCreator { + struct CampaignParameters { + // POPULATED ONCE CREATED + + // ID of the campaign. This can be left as a null bytes32 when creating campaigns + // on Merkl. + bytes32 campaignId; + // CHOSEN BY CAMPAIGN CREATOR + + // Address of the campaign creator, if marked as address(0), it will be overriden with the + // address of the `msg.sender` creating the campaign + address creator; + // Address of the token used as a reward + address rewardToken; + // Amount of `rewardToken` to distribute across all the epochs + // Amount distributed per epoch is `amount/numEpoch` + uint256 amount; + // Type of campaign + uint32 campaignType; + // Timestamp at which the campaign should start + uint32 startTimestamp; + // Duration of the campaign in seconds. Has to be a multiple of EPOCH = 3600 + uint32 duration; + // Extra data to pass to specify the campaign + bytes campaignData; + } + + function campaign(bytes32 id) external view returns (CampaignParameters memory); + + function acceptConditions() external; + + function createCampaign(CampaignParameters memory newCampaign) external returns (bytes32); +}