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: ampl distribution #290

Merged
merged 12 commits into from
Apr 11, 2024
Merged
1 change: 1 addition & 0 deletions .assets/010633e355dbf154c1151f809618bd82bf6c78f6.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions .assets/223a1aac9347ec68266ca4077ea5d03b73ca9e05.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions .assets/4048416bedf0808a47706e49a9f98ee64701623a.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions .assets/81174f6db1d6055f4be1846c1155b509e25ef500.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions .assets/ab240e54d4468f7b1cd32614f764cbef5a270f32.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions .assets/e8e7b9196f167723da0b9cc203b01762e5360791.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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,5 @@
## Raw diff

```json
{}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import {IERC20} from 'solidity-utils/contracts/oz-common/interfaces/IERC20.sol';
import {AaveV2Ethereum, AaveV2EthereumAssets} from 'aave-address-book/AaveV2Ethereum.sol';
import {IProposalGenericExecutor} from 'aave-helpers/interfaces/IProposalGenericExecutor.sol';

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);
}

/**
* @title Interim aAMPL distribution
* @author BGD Labs
* - Snapshot: https://snapshot.org/#/aave.eth/proposal/0xb7226dd6441b67225924082215f7a512bfd98252897ee43a879084e07ab53607
* - Discussion: https://governance.aave.com/t/arfc-aampl-interim-distribution/17184
*/
contract AaveV2Ethereum_InterimAAMPLDistribution_20240409 is IProposalGenericExecutor {
IDistributionCreator public constant DISTRIBUTION_CREATOR =
IDistributionCreator(0x8BB4C975Ff3c250e0ceEA271728547f3802B36Fd);
address public constant REWARD_TOKEN = AaveV2EthereumAssets.USDC_UNDERLYING;
uint256 public constant REWARD_AMOUNT_PLUS_FEES = 298_543_522_440;
string public constant FILE_URL =
'https://angle-blog.infura-ipfs.io/ipfs/Qmb9uJbEdppQsL8W4aVKxREoHo42iXtcp4CV1FLE5tY8Rt';

function execute() external {
// 1. send funds to executor
// 0.5% fee for angle merkle distributor
AaveV2Ethereum.COLLECTOR.transfer(
AaveV2EthereumAssets.USDC_A_TOKEN,
address(this),
REWARD_AMOUNT_PLUS_FEES + 1 // account for imprecision on transfer
);
AaveV2Ethereum.POOL.withdraw(REWARD_TOKEN, REWARD_AMOUNT_PLUS_FEES, address(this));
// 2. approve to merkl
IERC20(REWARD_TOKEN).approve(address(DISTRIBUTION_CREATOR), REWARD_AMOUNT_PLUS_FEES);
// 3. accept tos
DISTRIBUTION_CREATOR.acceptConditions();
// 4.create campaign
IDistributionCreator.CampaignParameters memory newCampaign = IDistributionCreator
.CampaignParameters({
campaignId: '',
creator: address(0),
sendra marked this conversation as resolved.
Show resolved Hide resolved
rewardToken: REWARD_TOKEN,
amount: REWARD_AMOUNT_PLUS_FEES,
campaignType: 4,
startTimestamp: uint32(block.timestamp + 2 hours),
duration: 1 hours,
sendra marked this conversation as resolved.
Show resolved Hide resolved
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,50 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {AaveV2Ethereum, AaveV2EthereumAssets} from 'aave-address-book/AaveV2Ethereum.sol';
import {GovernanceV3Ethereum} from 'aave-address-book/GovernanceV3Ethereum.sol';

import 'forge-std/Test.sol';
import {ProtocolV2TestBase, ReserveConfig} from 'aave-helpers/ProtocolV2TestBase.sol';
import {AaveV2Ethereum_InterimAAMPLDistribution_20240409, IDistributionCreator} from './AaveV2Ethereum_InterimAAMPLDistribution_20240409.sol';

/**
* @dev Test for AaveV2Ethereum_InterimAAMPLDistribution_20240409
* command: make test-contract filter=AaveV2Ethereum_InterimAAMPLDistribution_20240409
*/
contract AaveV2Ethereum_InterimAAMPLDistribution_20240409_Test is ProtocolV2TestBase {
AaveV2Ethereum_InterimAAMPLDistribution_20240409 internal proposal;

function setUp() public {
vm.createSelectFork(vm.rpcUrl('mainnet'), 19627195);
proposal = new AaveV2Ethereum_InterimAAMPLDistribution_20240409();
}

/**
* @dev executes the generic test suite including e2e and config snapshots
*/
function test_defaultProposalExecution() public {
defaultTest(
'AaveV2Ethereum_InterimAAMPLDistribution_20240409',
AaveV2Ethereum.POOL,
address(proposal)
);
}

function test_dataCorrectness() public {
executePayload(vm, address(proposal));

IDistributionCreator.CampaignParameters memory campaign = proposal
.DISTRIBUTION_CREATOR()
.campaign(bytes32(0xf202b6960fcee67260b0d7251d8eabba7b65d7a092357b57092d7e2f4a556f76));

assertEq(campaign.creator, GovernanceV3Ethereum.EXECUTOR_LVL_1);
assertEq(campaign.rewardToken, AaveV2EthereumAssets.USDC_UNDERLYING);
assertLe(campaign.amount, 300_000e6);
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());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
title: "Interim aAMPL distribution"
author: "BGD Labs"
discussions: "https://governance.aave.com/t/arfc-aampl-interim-distribution/17184"
snapshot: "https://snapshot.org/#/aave.eth/proposal/0xb7226dd6441b67225924082215f7a512bfd98252897ee43a879084e07ab53607"
---

## Simple Summary

Distribute 300.000 USDC to users affected by aAMPL problem.

## Motivation

On 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.

While further analysis is performed for the most reasonable strategy on giving withdrawal liquidity for aAMPL supplies, an interim distribution of 300’000 USD value is proposed as lower threshold, to allow aAMPL suppliers to proceed partially with their withdrawals.

With [aip 72](https://vote.onaave.com/proposal/?proposalId=72&ipfsHash=0xaa46d2cf629d68cc84bcc83156b2fd8e54819c5e848c229c7e62d1f6212886cc) having passed the governance process, aAMPL transfers are no longer permitted, which allows to snapshot the current aAMPL balances to perform a fair distribution between affected users.

This distribution have been defined the following way, with the help of @ChaosLabs and the Ampleforth team (for ubaAMPL holders):

- From each address holding aAMPL, a percentage over the total aAMPL supply has been calculated, to understand how is the proportion of each address.
- Using the previously calculated percentages, they have been applied over the total 300'000 USDC distribution: for example, for an address holding 5% of the total aAMPL, the claim has been calculated as 5% of 300'000; 15'000 USDC.
- For the holders of aAMPL through [Unbuttoned aAMPL (ubaAMPL)](https://etherscan.io/token/0xf03387d8d0ff326ab586a58e0ab4121d106147df#balances), the proportion over the total supply of ubaAMPL has been used to calculate the claims on aAMPL. For example, if an address held 20% of the total ubaAMPL supply, and ubaAMPL itself would be 20% of the aAMPL supply, the claims of that address would be the 4% of the total aAMPL. We appreciate the Ampleforth team providing us these "internal" holdings of ubaAMPL, given their knowledge of the system.
- Only claims over value of 30 USDC have been included, given that gas-wise, would not be profitable to claim lower amounts. However, those values lower than 30 USDC will be naturally taken into account for the final follow-up distribution.
- Once again, _this is an interim distribution, that will be follow up by another with more a precise numbers and bigger in size_.
- For the sake of reducing complexity, the Aave governance proposal will release the whole 300'000 USDC, and the Ampleforth team can just transfer to the Aave Collector the 40% of that amount, removing any dependency for users to claim as soon as possible.
- It is possible to check each address claims [HERE](https://github.com/bgd-labs/aave-proposals-v3/blob/8d5b3e902adf7c5c246e752b5d6b6e0e5d9831b7/src/20240409_AaveV2Ethereum_InterimAAMPLDistribution/distribution.pdf).
sendra marked this conversation as resolved.
Show resolved Hide resolved

## Specification

The distribution will be done via the [Merkl](https://app.merkl.xyz/) platform by Angle Labs, specialized on these operations and used before in other Aave DAO proposals, like the Merit program.

Users with a balance below 30$ will be excluded from this initial distribution as the gas-cost for claiming would not offset set amount claimed.

Therefore the proposal will perform the following steps upon execution:

- withdraw USDC from the collector (298.5k 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
- sign the tos of https://app.merkl.xyz/ via a onchain transaction, a requirement on the Merkl platform
- create a campaign to distribute funds to the affected users

2 hours after proposal execution, users will be able to claim the USDC on https://app.merkl.xyz/

sendra marked this conversation as resolved.
Show resolved Hide resolved
## References

- Implementation: [AaveV2Ethereum](https://github.com/bgd-labs/aave-proposals-v3/blob/main/src/20240409_AaveV2Ethereum_InterimAAMPLDistribution/AaveV2Ethereum_InterimAAMPLDistribution_20240409.sol)
- Tests: [AaveV2Ethereum](https://github.com/bgd-labs/aave-proposals-v3/blob/main/src/20240409_AaveV2Ethereum_InterimAAMPLDistribution/AaveV2Ethereum_InterimAAMPLDistribution_20240409.t.sol)
- [Snapshot](https://snapshot.org/#/aave.eth/proposal/0xb7226dd6441b67225924082215f7a512bfd98252897ee43a879084e07ab53607)
- [Discussion](https://governance.aave.com/t/arfc-aampl-interim-distribution/17184)
- [Distribution:IPFS](https://angle-blog.infura-ipfs.io/ipfs/Qmb9uJbEdppQsL8W4aVKxREoHo42iXtcp4CV1FLE5tY8Rt)
- [Distribution:formatted](https://github.com/bgd-labs/aave-proposals-v3/blob/main/src/20240409_AaveV2Ethereum_InterimAAMPLDistribution/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,60 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IERC20} from 'solidity-utils/contracts/oz-common/interfaces/IERC20.sol';
import {AaveV2Ethereum, AaveV2EthereumAssets} from 'aave-address-book/AaveV2Ethereum.sol';
import {GovV3Helpers, IPayloadsControllerCore, PayloadsControllerUtils} from 'aave-helpers/GovV3Helpers.sol';
import {EthereumScript} from 'aave-helpers/ScriptUtils.sol';
import {AaveV2Ethereum_InterimAAMPLDistribution_20240409} from './AaveV2Ethereum_InterimAAMPLDistribution_20240409.sol';

/**
* @dev Deploy Ethereum
* deploy-command: make deploy-ledger contract=src/20240409_AaveV2Ethereum_InterimAAMPLDistribution/InterimAAMPLDistribution_20240409.s.sol:DeployEthereum chain=mainnet
* verify-command: npx catapulta-verify -b broadcast/InterimAAMPLDistribution_20240409.s.sol/1/run-latest.json
*/
contract DeployEthereum is EthereumScript {
function run() external broadcast {
// deploy payloads
address payload0 = GovV3Helpers.deployDeterministic(
type(AaveV2Ethereum_InterimAAMPLDistribution_20240409).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/20240409_AaveV2Ethereum_InterimAAMPLDistribution/InterimAAMPLDistribution_20240409.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_InterimAAMPLDistribution_20240409).creationCode
);
payloads[0] = GovV3Helpers.buildMainnetPayload(vm, actionsEthereum);

// create proposal
vm.startBroadcast();
GovV3Helpers.createProposal(
vm,
payloads,
GovV3Helpers.ipfsHashFile(
vm,
'src/20240409_AaveV2Ethereum_InterimAAMPLDistribution/InterimAAMPLDistribution.md'
)
);
}
}
15 changes: 15 additions & 0 deletions src/20240409_AaveV2Ethereum_InterimAAMPLDistribution/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {ConfigFile} from '../../generator/types';
export const config: ConfigFile = {
rootOptions: {
configFile: 'src/20240409_AaveV2Ethereum_MerklTest/config.ts',
pools: ['AaveV2Ethereum'],
title: 'Interim aAMPL distribution',
shortName: 'InterimAAMPLDistribution',
date: '20240409',
author: 'BGD Labs',
discussion: 'https://governance.aave.com/t/arfc-aampl-interim-distribution/17184',
snapshot:
'https://snapshot.org/#/aave.eth/proposal/0xb7226dd6441b67225924082215f7a512bfd98252897ee43a879084e07ab53607',
},
poolOptions: {AaveV2Ethereum: {configs: {OTHERS: {}}, cache: {blockNumber: 19627195}}},
};
Binary file not shown.
21 changes: 21 additions & 0 deletions src/20240409_AaveV2Ethereum_InterimAAMPLDistribution/process.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { AaveV2Ethereum } from "@bgd-labs/aave-address-book";
import chaos from "./chaos.json" assert { type: "json" };

const processedUsers = chaos
.filter((f) => Math.floor(f.USDC_Per_User) !== 0)
.reduce((acc, f) => {
acc[f.Wallet] = { aAmplCompensation: Math.floor(f.USDC_Per_User) };
return acc;
}, {});

const totalAmount = chaos.reduce((acc, i) => {
return acc + Math.floor(i.USDC_Per_User);
}, 0);

const angleJSON = {
rewardToken: AaveV2Ethereum.ASSETS.USDC.UNDERLYING,
rewards: processedUsers,
};

// console.log(totalAmount);
console.log(JSON.stringify(angleJSON, null, 2));
Loading