-
Notifications
You must be signed in to change notification settings - Fork 66
feat: aToken Vault Revenue-Splitter Owner #101
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
Merged
Merged
Changes from 10 commits
Commits
Show all changes
28 commits
Select commit
Hold shift + click to select a range
c33f294
feat: aToken Vault Revenut-Splitter Owner - Initial impl
donosonaumczuk 3db0ba1
misc: Some TODO comments removed
donosonaumczuk 0260658
misc: Natspec added
donosonaumczuk 4a21714
misc: Little comment ammend
donosonaumczuk af9ec4e
test: Some ATokenVaultRevenueSplitterOwner tests added - still some c…
donosonaumczuk 15be1cb
feat: Do not allow empty recipients for the revenue splitter
donosonaumczuk 894b3cf
test: Constructor params and recipients setup tested
donosonaumczuk 0d52af1
test: ERC-20 split revenue tested
donosonaumczuk 54ef64b
test: Native currency split revenue tested
donosonaumczuk df20ef9
test: Fuzzed BPS shares test added
donosonaumczuk 0f33b6e
test: Test that some basic reentrancy fails
donosonaumczuk badb787
misc: RevenueSplit event renamed to RevenueSplitTransferred
donosonaumczuk 1eedf5f
feat: Native currency revenue split removed
donosonaumczuk c02d423
feat: Revoke support for native currency
donosonaumczuk efd4dff
misc: Comments added and improved
donosonaumczuk cc16333
feat: Do not accept address(0) as recipient
donosonaumczuk 460fe18
feat: Call withdrawFees only when getClaimableFees > 0
donosonaumczuk acb49b1
feat: Calculate amount to split based on prev accumulated balance & o…
donosonaumczuk 3922643
feat: Leave unit undistributed to avoid aToken transfer issues
donosonaumczuk 193eea4
misc: Comment punctuation & author correction
donosonaumczuk c68956b
test: ATokenVaultRevenueSplitterOwnerTest tests fixed after changes
donosonaumczuk 730483b
test: Rounding error is not accumulated after many splits
donosonaumczuk 657db34
feat: Do not allow to split assets with 0 balance
donosonaumczuk ddca6ca
feat: Disallow duplicated recipients
donosonaumczuk 8243213
test: Tests added for duplicated recipients check
donosonaumczuk 05ae204
misc: Comment related to undistributed dust simplified
donosonaumczuk 080865b
feat: Merge changes after audit round
donosonaumczuk 817a624
chore: Audit report added for aToken Vault Revenue Splitter
donosonaumczuk File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,189 @@ | ||
| // SPDX-License-Identifier: UNLICENSED | ||
| // All Rights Reserved © AaveCo | ||
|
|
||
| pragma solidity ^0.8.10; | ||
|
|
||
| import {Ownable} from "@openzeppelin/access/Ownable.sol"; | ||
| import {IATokenVault} from "./interfaces/IATokenVault.sol"; | ||
| import {IERC20} from "@openzeppelin/token/ERC20/IERC20.sol"; | ||
| import {SafeERC20} from "@openzeppelin/token/ERC20/utils/SafeERC20.sol"; | ||
|
|
||
| /** | ||
| * @title ATokenVaultRevenueSplitterOwner | ||
| * @author Aave Protocol | ||
| * @notice ATokenVault owner with revenue split capabilities. | ||
| */ | ||
| contract ATokenVaultRevenueSplitterOwner is Ownable { | ||
| using SafeERC20 for IERC20; | ||
|
|
||
| /** | ||
| * @dev Emitted at construction time for each recipient set | ||
| * @param recipient The address of the recipient set | ||
| * @param shareInBps The recipient's share of the revenue in basis points | ||
| */ | ||
| event RecipientSet(address indexed recipient, uint16 shareInBps); | ||
|
|
||
| /** | ||
| * @dev Emitted when revenue is split for each recipient and asset | ||
| * @param recipient The address of the recipient receiving the revenue | ||
| * @param asset The asset being split | ||
| * @param amount The amount of revenue sent to the recipient in the split asset | ||
| */ | ||
| event RevenueSplit(address indexed recipient, address indexed asset, uint256 amount); | ||
|
|
||
| /** | ||
| * @dev The sum of all recipients' shares in basis points, represents 100.00%. Each basis point is 0.01%. | ||
| */ | ||
| uint256 public constant TOTAL_SHARE_IN_BPS = 10_000; | ||
|
|
||
| /** | ||
| * @dev The aToken Vault to own, whose revenue is split. | ||
| */ | ||
| IATokenVault public immutable VAULT; | ||
|
|
||
| /** | ||
| * @dev A struct to represent a recipient and its share of the revenue in basis points. | ||
| * @param addr The address of the recipient | ||
| * @param shareInBps The recipient's share of the revenue in basis points | ||
| */ | ||
| struct Recipient { | ||
| address addr; | ||
| uint16 shareInBps; | ||
| } | ||
|
|
||
| /** | ||
| * @dev The recipients set for this revenue splitter. Set at construction time only, cannot be modified afterwards. | ||
| */ | ||
| Recipient[] internal _recipients; | ||
|
|
||
| /** | ||
| * @dev Constructor. | ||
| * @param vault The address of the aToken Vault to own, whose revenue is split. | ||
| * @param owner The address owning this contract, the effective owner of the vault. | ||
| * @param recipients The recipients to set for the revenue split. Cannot be modified afterwards. | ||
| */ | ||
| constructor(address vault, address owner, Recipient[] memory recipients) { | ||
| VAULT = IATokenVault(vault); | ||
| require(recipients.length > 0, "MISSING_RECIPIENTS"); | ||
| _setRecipients(recipients); | ||
| _transferOwnership(owner); | ||
| } | ||
|
|
||
| /** | ||
| * @dev Transfers the ownership of the aToken vault to a new owner. Claims all fees and rewards prior to transfer, | ||
| * to secure already accrued fees and rewards for the configured split recipients. | ||
| * @dev Only callable by the owner of this contract. | ||
| * @dev DO NOT confuse with `transferOwnership` which transfers the ownership of this contract instead. | ||
| * @param newVaultOwner The address of the new aToken vault owner. | ||
| */ | ||
| function transferVaultOwnership(address newVaultOwner) public onlyOwner { | ||
| _claimRewards(); | ||
| _withdrawFees(); | ||
| Ownable(address(VAULT)).transferOwnership(newVaultOwner); | ||
| } | ||
|
|
||
| /** | ||
| * @dev Withdraws all vault fees to this contract, so they can be split among the configured recipients. | ||
| */ | ||
| function withdrawFees() public { | ||
| _withdrawFees(); | ||
| } | ||
|
|
||
| /** | ||
| * @dev Claims all vault rewards to this contract, so they can be split among the configured recipients. | ||
| */ | ||
| function claimRewards() external { | ||
| _claimRewards(); | ||
| } | ||
|
|
||
| /** | ||
| * @dev Splits the revenue from the given assets among the configured recipients. Assets must follow the ERC-20 | ||
| * interface and be held by this contract. | ||
| * @param assets The assets to split the revenue from. | ||
| */ | ||
| function splitRevenue(address[] calldata assets) public { | ||
DhairyaSethi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Recipient[] memory recipients = _recipients; | ||
DhairyaSethi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| for (uint256 i = 0; i < assets.length; i++) { | ||
DhairyaSethi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| uint256 amountToSplit = IERC20(assets[i]).balanceOf(address(this)); | ||
| for (uint256 j = 0; j < recipients.length; j++) { | ||
| uint256 amountForRecipient = amountToSplit * recipients[j].shareInBps / TOTAL_SHARE_IN_BPS; | ||
DhairyaSethi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
DhairyaSethi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if (amountForRecipient > 0) { | ||
| IERC20(assets[i]).safeTransfer(recipients[j].addr, amountForRecipient); | ||
DhairyaSethi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| emit RevenueSplit(recipients[j].addr, assets[i], amountForRecipient); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // TODO: Should we support splitting native currency revenue or only ERC20s? | ||
donosonaumczuk marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| // TODO: address(0) used in the event instead of ad-hoc event for native revenue? | ||
| // TODO: Should we assume recipients will succeed at receiving native? If one fails, the whole call fails. | ||
| /** | ||
| * @dev Splits the native currency revenue among the configured recipients. | ||
| */ | ||
| function splitRevenue() public { | ||
| uint256 amountToSplit = address(this).balance; | ||
| for (uint256 j = 0; j < _recipients.length; j++) { | ||
| uint256 amountForRecipient = amountToSplit * _recipients[j].shareInBps / TOTAL_SHARE_IN_BPS; | ||
| if (amountForRecipient > 0) { | ||
| (bool transferSucceeded, ) = _recipients[j].addr.call{value: amountForRecipient}(""); | ||
| require(transferSucceeded, "NATIVE_TRANSFER_FAILED"); | ||
| } | ||
| emit RevenueSplit(_recipients[j].addr, address(0), amountForRecipient); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @dev Rescues assets that may have accidentally been transferred to the vault. | ||
| * @dev Only callable by the owner of this contract. | ||
| * @dev The asset to rescue cannot be the vault's aToken. | ||
| * @dev Fees cannot be "rescued" as they are accrued in the vault's aToken. Rewards cannot be "rescued" as they are | ||
| * not held by the vault contract. Thus, already accrued fees and rewards cannot be taken from split recipients. | ||
| * @param asset The asset to rescue from the vault. | ||
| * @param to The address to send the rescued assets to. | ||
| * @param amount The amount of assets to rescue from the vault. | ||
| */ | ||
| function emergencyRescue(address asset, address to, uint256 amount) public onlyOwner { | ||
| VAULT.emergencyRescue(asset, to, amount); | ||
| } | ||
|
|
||
| /** | ||
| * @dev Sets the fee for the vault. | ||
| * @dev Only callable by the owner of this contract. | ||
| * @param newFee The new fee for the vault. | ||
| */ | ||
| function setFee(uint256 newFee) public onlyOwner { | ||
| VAULT.setFee(newFee); | ||
| } | ||
|
|
||
| /** | ||
| * @dev Getter for the revenue split configured recipients. | ||
| * @return The configured recipients with their corresponding share in basis points. | ||
| */ | ||
| function getRecipients() public view returns (Recipient[] memory) { | ||
| return _recipients; | ||
| } | ||
|
|
||
| function _claimRewards() internal { | ||
| VAULT.claimRewards(address(this)); | ||
| } | ||
|
|
||
| function _withdrawFees() internal { | ||
| uint256 feesToWithdraw = VAULT.getClaimableFees(); | ||
| VAULT.withdrawFees(address(this), feesToWithdraw); | ||
| } | ||
|
|
||
| /** | ||
| * @dev Does not check for duplicates in the recipients array. Sum of shares must represent 100.00% in basis points. | ||
| */ | ||
| function _setRecipients(Recipient[] memory recipients) internal { | ||
| uint256 accumulatedShareInBps = 0; | ||
| for (uint256 i = 0; i < recipients.length; i++) { | ||
| require(recipients[i].shareInBps > 0, "BPS_SHARE_CANNOT_BE_ZERO"); | ||
| accumulatedShareInBps += recipients[i].shareInBps; | ||
| _recipients.push(recipients[i]); | ||
| emit RecipientSet(recipients[i].addr, recipients[i].shareInBps); | ||
| } | ||
| require(accumulatedShareInBps == TOTAL_SHARE_IN_BPS, "WRONG_BPS_SUM"); | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.