diff --git a/src/20240220_AaveV3Ethereum_OrbitProgram/AaveV3Ethereum_OrbitProgram_20240220.sol b/src/20240220_AaveV3Ethereum_OrbitProgram/AaveV3Ethereum_OrbitProgram_20240220.sol new file mode 100644 index 000000000..cddddbf46 --- /dev/null +++ b/src/20240220_AaveV3Ethereum_OrbitProgram/AaveV3Ethereum_OrbitProgram_20240220.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IERC20} from 'solidity-utils/contracts/oz-common/interfaces/IERC20.sol'; +import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; + +import {OrbitProgramData} from './OrbitProgramData.sol'; + +import {IProposalGenericExecutor} from 'aave-helpers/interfaces/IProposalGenericExecutor.sol'; + +/// Helper interface to withdraw ETH +interface IWETH { + function withdraw(uint256) external; +} + +/** + * @title Orbit Program + * @author karpatkey_TokenLogic_ACI + * - Snapshot: https://snapshot.org/#/aave.eth/proposal/0x412b38c7a0cf1840b102e28ea7ef0373e3ab4b9544873e8cc1544972b777d9a1 + * - Discussion: https://governance.aave.com/t/arfc-orbit-program-renewal/16550 + */ +contract AaveV3Ethereum_OrbitProgram_20240220 is IProposalGenericExecutor { + error EthTransferFailed(address account); + + function execute() external { + AaveV3Ethereum.COLLECTOR.transfer( + AaveV3EthereumAssets.WETH_UNDERLYING, + address(this), + OrbitProgramData.TOTAL_WETH_REBATE + ); + + IWETH(AaveV3EthereumAssets.WETH_UNDERLYING).withdraw(OrbitProgramData.TOTAL_WETH_REBATE); + + OrbitProgramData.GasUsage[] memory usage = OrbitProgramData.getGasUsageData(); + uint256 usageLength = usage.length; + for (uint256 i = 0; i < usageLength; i++) { + (bool ok, ) = usage[i].account.call{value: usage[i].usage}(''); + if (!ok) revert EthTransferFailed(usage[i].account); + } + + uint256 actualStreamAmount = (OrbitProgramData.STREAM_AMOUNT / + OrbitProgramData.STREAM_DURATION) * OrbitProgramData.STREAM_DURATION; + + address[] memory orbitAddresses = OrbitProgramData.getOrbitAddresses(); + uint256 orbitAddressesLength = orbitAddresses.length; + for (uint256 i = 0; i < orbitAddressesLength; i++) { + AaveV3Ethereum.COLLECTOR.transfer( + AaveV3EthereumAssets.GHO_UNDERLYING, + orbitAddresses[i], + OrbitProgramData.RETRO_PAYMENT + ); + + AaveV3Ethereum.COLLECTOR.createStream( + orbitAddresses[i], + actualStreamAmount, + AaveV3EthereumAssets.GHO_UNDERLYING, + block.timestamp, + block.timestamp + OrbitProgramData.STREAM_DURATION + ); + } + } + + receive() external payable {} +} diff --git a/src/20240220_AaveV3Ethereum_OrbitProgram/AaveV3Ethereum_OrbitProgram_20240220.t.sol b/src/20240220_AaveV3Ethereum_OrbitProgram/AaveV3Ethereum_OrbitProgram_20240220.t.sol new file mode 100644 index 000000000..3f3e6198a --- /dev/null +++ b/src/20240220_AaveV3Ethereum_OrbitProgram/AaveV3Ethereum_OrbitProgram_20240220.t.sol @@ -0,0 +1,94 @@ +// 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 {ProtocolV3TestBase} from 'aave-helpers/ProtocolV3TestBase.sol'; + +import {AaveV3Ethereum_OrbitProgram_20240220} from './AaveV3Ethereum_OrbitProgram_20240220.sol'; +import {OrbitProgramData} from './OrbitProgramData.sol'; + +/** + * @dev Test for AaveV3Ethereum_OrbitProgram_20240220 + * command: make test-contract filter=AaveV3Ethereum_OrbitProgram_20240220 + */ +contract AaveV3Ethereum_OrbitProgram_20240220_Test is ProtocolV3TestBase { + uint256 public constant TOTAL_GHO_WITHDRAWN = 20_000 ether; + + AaveV3Ethereum_OrbitProgram_20240220 internal proposal; + + function setUp() public { + vm.createSelectFork(vm.rpcUrl('mainnet'), 19215132); + proposal = new AaveV3Ethereum_OrbitProgram_20240220(); + } + + /** + * @dev executes the generic test suite including e2e and config snapshots + */ + function test_defaultProposalExecution() public { + uint256 collectorGhoBalanceBefore = IERC20(AaveV3EthereumAssets.GHO_UNDERLYING).balanceOf( + address(AaveV3Ethereum.COLLECTOR) + ); + uint256 collectorWethBalanceBefore = IERC20(AaveV3EthereumAssets.WETH_UNDERLYING).balanceOf( + address(AaveV3Ethereum.COLLECTOR) + ); + + uint256[] memory ethBalancesBeforeUsers = new uint256[](7); + OrbitProgramData.GasUsage[] memory usage = OrbitProgramData.getGasUsageData(); + for (uint256 i = 0; i < usage.length; i++) { + ethBalancesBeforeUsers[i] = usage[i].account.balance; + } + + uint256[] memory ghoBalancesBeforeUsers = new uint256[](4); + address[] memory ghoPaymentAddresses = OrbitProgramData.getOrbitAddresses(); + for (uint256 i = 0; i < ghoPaymentAddresses.length; i++) { + ghoBalancesBeforeUsers[i] = IERC20(AaveV3EthereumAssets.GHO_UNDERLYING).balanceOf( + ghoPaymentAddresses[i] + ); + } + + uint256 nextStreamId = AaveV3Ethereum.COLLECTOR.getNextStreamId(); + vm.expectRevert(); + AaveV3Ethereum.COLLECTOR.getStream(nextStreamId); + + executePayload(vm, address(proposal)); + + assertEq( + IERC20(AaveV3EthereumAssets.GHO_UNDERLYING).balanceOf(address(AaveV3Ethereum.COLLECTOR)), + collectorGhoBalanceBefore - TOTAL_GHO_WITHDRAWN, + 'GHO balance of Collector is not equal to previous minus to withdraw' + ); + assertEq( + IERC20(AaveV3EthereumAssets.WETH_UNDERLYING).balanceOf(address(AaveV3Ethereum.COLLECTOR)), + collectorWethBalanceBefore - OrbitProgramData.TOTAL_WETH_REBATE, + 'WETH balance of Collector is not equal to previous minus to withdraw' + ); + + for (uint256 i = 0; i < usage.length; i++) { + assertGt( + usage[i].account.balance, + ethBalancesBeforeUsers[i], + 'REBATE recipient balance is not greater than before' + ); + } + + vm.warp(block.timestamp + 7 days); + + /// Their GHO balance has increased and call also withdraw from stream as it now exists + for (uint256 i = 0; i < ghoPaymentAddresses.length; i++) { + assertEq( + IERC20(AaveV3EthereumAssets.GHO_UNDERLYING).balanceOf(ghoPaymentAddresses[i]), + ghoBalancesBeforeUsers[i] + OrbitProgramData.RETRO_PAYMENT, + 'GHO balance of Orbit recipient is not greater than before' + ); + + vm.prank(ghoPaymentAddresses[i]); + AaveV3Ethereum.COLLECTOR.withdrawFromStream(nextStreamId + i, 1); + assertEq( + IERC20(AaveV3EthereumAssets.GHO_UNDERLYING).balanceOf(ghoPaymentAddresses[i]), + ghoBalancesBeforeUsers[i] + OrbitProgramData.RETRO_PAYMENT + 1 + ); + } + } +} diff --git a/src/20240220_AaveV3Ethereum_OrbitProgram/OrbitProgram.md b/src/20240220_AaveV3Ethereum_OrbitProgram/OrbitProgram.md new file mode 100644 index 000000000..991a672f3 --- /dev/null +++ b/src/20240220_AaveV3Ethereum_OrbitProgram/OrbitProgram.md @@ -0,0 +1,56 @@ +--- +title: "Orbit Program" +author: "karpatkey_TokenLogic_ACI" +discussions: "https://governance.aave.com/t/arfc-orbit-program-renewal/16550" +snapshot: "https://snapshot.org/#/aave.eth/proposal/0x412b38c7a0cf1840b102e28ea7ef0373e3ab4b9544873e8cc1544972b777d9a1" +--- + +## Simple Summary + +This proposal includes the renewal of the Orbit program for recognized delegates. Compensating them with GHO and ETH reimbursement of Gas costs associated with their governance activity. + +## Motivation + +The Orbit program Snapshot and discussion can be found [here](https://snapshot.org/#/aave.eth/proposal/0x412b38c7a0cf1840b102e28ea7ef0373e3ab4b9544873e8cc1544972b777d9a1) and [here](https://governance.aave.com/t/arfc-orbit-program-renewal/16550) respectively. +LBS updated their address [here](https://governance.aave.com/t/arfc-orbit-program-renewal/16550/5?u=lbsblockchain). + +The following table outlines the proposed compensation for eligible delegates matching the requirements: + +| Delegate Platform | Retro-Payment (GHO) | New Stream (GHO) | +| ----------------- | ------------------- | ---------------- | +| EzR3al | 5000 | 15000 | +| Stable Labs | 5000 | 15000 | +| LBS Blockchain | 5000 | 15000 | +| Michigan | 5000 | 15000 | + +In terms of gas rebate, we included Gov V2 reimbursement & payload-related activity in the following table: + +| Delegate / Service Provider | Gas Used (ETH) | +| --------------------------- | -------------- | +| ACI | 3.365 | +| Tokenlogic | 0.586 | +| Michigan | 0.276 | +| Wintermute | 0.2518 | +| LBS | 0.031 | +| StableLabs | 0.0342 | +| ezr3al | 0.3833 | +| Total | 4.9273 | + +Note Michigan has rebranded as Arana as can be seen in the forum [here](https://governance.aave.com/t/arfc-orbit-program-renewal/16550/6). + +## Specification + +- Create GHO streams for Orbit participants of 15,000 GHO for 90 days as detailed in the table above +- Transfer GHO for Orbit participants of 5,000 as retroactive payment as detailed in the table above +- Transfer ETH to delegates/service providers as detailed in the table above + +## References + +- Implementation: [AaveV3Ethereum](https://github.com/bgd-labs/aave-proposals-v3/blob/main/src/20240220_AaveV3Ethereum_OrbitProgram/AaveV3Ethereum_OrbitProgram_20240220.sol) +- Tests: [AaveV3Ethereum](https://github.com/bgd-labs/aave-proposals-v3/blob/main/src/20240220_AaveV3Ethereum_OrbitProgram/AaveV3Ethereum_OrbitProgram_20240220.t.sol) +- [Snapshot](https://snapshot.org/#/aave.eth/proposal/0x412b38c7a0cf1840b102e28ea7ef0373e3ab4b9544873e8cc1544972b777d9a1) +- [Discussion](https://governance.aave.com/t/arfc-orbit-program-renewal/16550) + +## Copyright + +Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/). diff --git a/src/20240220_AaveV3Ethereum_OrbitProgram/OrbitProgramData.sol b/src/20240220_AaveV3Ethereum_OrbitProgram/OrbitProgramData.sol new file mode 100644 index 000000000..822741a64 --- /dev/null +++ b/src/20240220_AaveV3Ethereum_OrbitProgram/OrbitProgramData.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +library OrbitProgramData { + struct GasUsage { + address account; + uint256 usage; + } + + uint256 public constant STREAM_DURATION = 90 days; + uint256 public constant RETRO_PAYMENT = 5_000 ether; + uint256 public constant STREAM_AMOUNT = 15_000 ether; + uint256 public constant TOTAL_WETH_REBATE = 4.9273 ether; + address public constant EZREAL = 0x8659D0BB123Da6D16D9394C7838BA286c2207d0E; + address public constant STABLE_LABS = 0xECC2a9240268BC7a26386ecB49E1Befca2706AC9; + address public constant LBS = 0x8b37a5Af68D315cf5A64097D96621F64b5502a22; + address public constant MICHIGAN = 0x0579A616689f7ed748dC07692A3F150D44b0CA09; + address public constant ACI = 0x57ab7ee15cE5ECacB1aB84EE42D5A9d0d8112922; + address public constant TOKEN_LOGIC = 0x2cc1ADE245020FC5AAE66Ad443e1F66e01c54Df1; + address public constant WINTERMUTE = 0xB933AEe47C438f22DE0747D57fc239FE37878Dd1; + + function getGasUsageData() internal pure returns (GasUsage[] memory) { + GasUsage[] memory usage = new GasUsage[](7); + usage[0] = GasUsage(ACI, 3.365 ether); + usage[1] = GasUsage(TOKEN_LOGIC, 0.586 ether); + usage[2] = GasUsage(MICHIGAN, 0.276 ether); + usage[3] = GasUsage(WINTERMUTE, 0.2518 ether); + usage[4] = GasUsage(LBS, 0.031 ether); + usage[5] = GasUsage(STABLE_LABS, 0.0342 ether); + usage[6] = GasUsage(EZREAL, 0.3833 ether); + + return usage; + } + + function getOrbitAddresses() internal pure returns (address[] memory) { + address[] memory streamAddresses = new address[](4); + streamAddresses[0] = EZREAL; + streamAddresses[1] = STABLE_LABS; + streamAddresses[2] = MICHIGAN; + streamAddresses[3] = LBS; + + return streamAddresses; + } +} diff --git a/src/20240220_AaveV3Ethereum_OrbitProgram/OrbitProgram_20240220.s.sol b/src/20240220_AaveV3Ethereum_OrbitProgram/OrbitProgram_20240220.s.sol new file mode 100644 index 000000000..e785b0d07 --- /dev/null +++ b/src/20240220_AaveV3Ethereum_OrbitProgram/OrbitProgram_20240220.s.sol @@ -0,0 +1,55 @@ +// 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 {AaveV3Ethereum_OrbitProgram_20240220} from './AaveV3Ethereum_OrbitProgram_20240220.sol'; + +/** + * @dev Deploy Ethereum + * deploy-command: make deploy-ledger contract=src/20240220_AaveV3Ethereum_OrbitProgram/OrbitProgram_20240220.s.sol:DeployEthereum chain=mainnet + * verify-command: npx catapulta-verify -b broadcast/OrbitProgram_20240220.s.sol/1/run-latest.json + */ +contract DeployEthereum is EthereumScript { + function run() external broadcast { + // deploy payloads + address payload0 = GovV3Helpers.deployDeterministic( + type(AaveV3Ethereum_OrbitProgram_20240220).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/20240220_AaveV3Ethereum_OrbitProgram/OrbitProgram_20240220.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(AaveV3Ethereum_OrbitProgram_20240220).creationCode + ); + payloads[0] = GovV3Helpers.buildMainnetPayload(vm, actionsEthereum); + + // create proposal + vm.startBroadcast(); + GovV3Helpers.createProposal( + vm, + payloads, + GovV3Helpers.ipfsHashFile(vm, 'src/20240220_AaveV3Ethereum_OrbitProgram/OrbitProgram.md') + ); + } +} diff --git a/src/20240220_AaveV3Ethereum_OrbitProgram/config.ts b/src/20240220_AaveV3Ethereum_OrbitProgram/config.ts new file mode 100644 index 000000000..ed4736014 --- /dev/null +++ b/src/20240220_AaveV3Ethereum_OrbitProgram/config.ts @@ -0,0 +1,14 @@ +import {ConfigFile} from '../../generator/types'; +export const config: ConfigFile = { + rootOptions: { + pools: ['AaveV3Ethereum'], + title: 'Orbit Program', + shortName: 'OrbitProgram', + date: '20240220', + author: 'karpatkey_TokenLogic_ACI', + discussion: 'https://governance.aave.com/t/arfc-orbit-program-renewal/16550', + snapshot: + 'https://snapshot.org/#/aave.eth/proposal/0x412b38c7a0cf1840b102e28ea7ef0373e3ab4b9544873e8cc1544972b777d9a1', + }, + poolOptions: {AaveV3Ethereum: {configs: {OTHERS: {}}, cache: {blockNumber: 19273121}}}, +};