Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: aAMPL second distribution #325

Merged
merged 4 commits into from
Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## Raw diff

```json
{}
```
Original file line number Diff line number Diff line change
@@ -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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amount here should be updated too

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/).
Original file line number Diff line number Diff line change
@@ -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'
)
);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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());
}
}
14 changes: 14 additions & 0 deletions src/20240429_AaveV2Ethereum_AAMPLSecondDistribution/config.ts
Original file line number Diff line number Diff line change
@@ -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}}},
};
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -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);
}
Loading