This repository was archived by the owner on Jul 15, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 19
feat: refresh rewards automation #34
Open
brotherlymite
wants to merge
15
commits into
main
Choose a base branch
from
feat/refresh-automation
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
3d15eeb
forge install: chainlink
brotherlymite 1ab53b1
fix: remappings
brotherlymite e562fc9
feat: init refresh keeper
brotherlymite edc61ee
test: refresh rewards automation by creating new LM
brotherlymite d686260
docs: some comments and refactoring
brotherlymite 315c7db
fix: lint
brotherlymite 61847eb
Merge branch 'main' into feat/refresh-automation
brotherlymite 0223bf9
fix: robot
brotherlymite e663747
feat: gas capped robot
brotherlymite c6f6a78
fix: add missing break and also fix lint
brotherlymite aeeb6ca
feat: robot for gelato
brotherlymite ab89c0b
feat: deploy scripts for robots
brotherlymite ea04aeb
fix: readme
brotherlymite ea87897
Merge branch 'main' into feat/refresh-automation
brotherlymite 488119d
fix: gelato robots
brotherlymite 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 |
|---|---|---|
| @@ -1,6 +1,17 @@ | ||
| [submodule "lib/forge-std"] | ||
| path = lib/forge-std | ||
| url = https://github.com/foundry-rs/forge-std | ||
| [submodule "lib/aave-v3-periphery"] | ||
| path = lib/aave-v3-periphery | ||
| url = https://github.com/aave/aave-v3-periphery | ||
| [submodule "lib/chainlink"] | ||
| path = lib/chainlink | ||
| url = https://github.com/smartcontractkit/chainlink | ||
| branch = v1.13.0 | ||
| [submodule "lib/aave-helpers"] | ||
| path = lib/aave-helpers | ||
| url = https://github.com/bgd-labs/aave-helpers | ||
| [submodule "lib/aave-governance-v3-robot"] | ||
| path = lib/aave-governance-v3-robot | ||
| url = https://github.com/bgd-labs/aave-governance-v3-robot | ||
| branch = fix/operator |
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
Submodule aave-governance-v3-robot
added at
b3c251
Submodule aave-helpers
updated
23 files
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
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,108 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity ^0.8.0; | ||
|
|
||
| import {EthereumScript, PolygonScript, AvalancheScript, ArbitrumScript, OptimismScript, MetisScript, BaseScript, BNBScript, GnosisScript} from 'aave-helpers/ScriptUtils.sol'; | ||
| import {AaveV3Ethereum, IPool} from 'aave-address-book/AaveV3Ethereum.sol'; | ||
| import {AaveV3Polygon} from 'aave-address-book/AaveV3Polygon.sol'; | ||
| import {AaveV3Avalanche} from 'aave-address-book/AaveV3Avalanche.sol'; | ||
| import {AaveV3Optimism} from 'aave-address-book/AaveV3Optimism.sol'; | ||
| import {AaveV3Arbitrum} from 'aave-address-book/AaveV3Arbitrum.sol'; | ||
| import {AaveV3Metis} from 'aave-address-book/AaveV3Metis.sol'; | ||
| import {AaveV3Base} from 'aave-address-book/AaveV3Base.sol'; | ||
| import {AaveV3BNB} from 'aave-address-book/AaveV3BNB.sol'; | ||
| import {AaveV3Gnosis} from 'aave-address-book/AaveV3Gnosis.sol'; | ||
| import {GasCappedRefreshRewardsRobot} from '../src/robots/GasCappedRefreshRewardsRobot.sol'; | ||
| import {GelatoGasCappedRefreshRewardsRobot} from '../src/robots/GelatoGasCappedRefreshRewardsRobot.sol'; | ||
| import {RefreshRewardsRobot} from '../src/robots/RefreshRewardsRobot.sol'; | ||
|
|
||
| // make deploy-ledger contract=scripts/Robots.s.sol:DeployMainnet chain=mainnet | ||
| contract DeployMainnet is EthereumScript { | ||
| function run() external broadcast { | ||
| GasCappedRefreshRewardsRobot robot = new GasCappedRefreshRewardsRobot( | ||
| AaveV3Ethereum.STATIC_A_TOKEN_FACTORY, | ||
| AaveV3Ethereum.DEFAULT_INCENTIVES_CONTROLLER, | ||
| 0x169E633A2D1E6c10dD91238Ba11c4A708dfEF37C // chainlink fast gas feed | ||
| ); | ||
| robot.setMaxGasPrice(150 gwei); | ||
| } | ||
| } | ||
|
|
||
| // make deploy-ledger contract=scripts/Robots.s.sol:DeployPolygon chain=polygon | ||
| contract DeployPolygon is PolygonScript { | ||
| function run() external broadcast { | ||
| new RefreshRewardsRobot( | ||
| AaveV3Polygon.STATIC_A_TOKEN_FACTORY, | ||
| AaveV3Polygon.DEFAULT_INCENTIVES_CONTROLLER | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| // make deploy-ledger contract=scripts/Robots.s.sol:DeployAvalanche chain=avalanche | ||
| contract DeployAvalanche is AvalancheScript { | ||
| function run() external broadcast { | ||
| new RefreshRewardsRobot( | ||
| AaveV3Avalanche.STATIC_A_TOKEN_FACTORY, | ||
| AaveV3Avalanche.DEFAULT_INCENTIVES_CONTROLLER | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| // make deploy-ledger contract=scripts/Robots.s.sol:DeployOptimism chain=optimism | ||
| contract DeployOptimism is OptimismScript { | ||
| function run() external broadcast { | ||
| new RefreshRewardsRobot( | ||
| AaveV3Optimism.STATIC_A_TOKEN_FACTORY, | ||
| AaveV3Optimism.DEFAULT_INCENTIVES_CONTROLLER | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| // make deploy-ledger contract=scripts/Robots.s.sol:DeployArbitrum chain=arbitrum | ||
| contract DeployArbitrum is ArbitrumScript { | ||
| function run() external broadcast { | ||
| new RefreshRewardsRobot( | ||
| AaveV3Arbitrum.STATIC_A_TOKEN_FACTORY, | ||
| AaveV3Arbitrum.DEFAULT_INCENTIVES_CONTROLLER | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| // make deploy-ledger contract=scripts/Robots.s.sol:DeployBase chain=base | ||
| contract DeployBase is BaseScript { | ||
| function run() external broadcast { | ||
| new RefreshRewardsRobot( | ||
| AaveV3Base.STATIC_A_TOKEN_FACTORY, | ||
| AaveV3Base.DEFAULT_INCENTIVES_CONTROLLER | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| // make deploy-ledger contract=scripts/Robots.s.sol:DeployBNB chain=bnb | ||
| contract DeployBNB is BNBScript { | ||
| function run() external broadcast { | ||
| new RefreshRewardsRobot( | ||
| AaveV3BNB.STATIC_A_TOKEN_FACTORY, | ||
| AaveV3BNB.DEFAULT_INCENTIVES_CONTROLLER | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| // make deploy-ledger contract=scripts/Robots.s.sol:DeployMetis chain=metis | ||
| contract DeployMetis is MetisScript { | ||
| function run() external broadcast { | ||
| new GelatoGasCappedRefreshRewardsRobot( | ||
| AaveV3Metis.STATIC_A_TOKEN_FACTORY, | ||
| AaveV3Metis.DEFAULT_INCENTIVES_CONTROLLER | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| // make deploy-ledger contract=scripts/Robots.s.sol:DeployGnosis chain=gnosis | ||
| contract DeployGnosis is GnosisScript { | ||
| function run() external broadcast { | ||
| new GelatoGasCappedRefreshRewardsRobot( | ||
| AaveV3Gnosis.STATIC_A_TOKEN_FACTORY, | ||
| AaveV3Gnosis.DEFAULT_INCENTIVES_CONTROLLER | ||
| ); | ||
| } | ||
| } |
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,50 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity ^0.8.0; | ||
|
|
||
| import {AutomationCompatibleInterface} from 'chainlink/src/v0.8/interfaces/automation/AutomationCompatibleInterface.sol'; | ||
| import {IStaticATokenFactory} from './IStaticATokenFactory.sol'; | ||
| import {IRewardsController} from 'aave-v3-periphery/contracts/rewards/interfaces/IRewardsController.sol'; | ||
|
|
||
| /** | ||
| * @title IRefreshRewardsRobot | ||
| * @author BGD Labs | ||
| * @notice Defines the interface for the contract to automate refresh rewards for staticATokens. | ||
| **/ | ||
| interface IRefreshRewardsRobot is AutomationCompatibleInterface { | ||
| /** | ||
| * @notice emitted when rewards are refreshed for a staticAToken. | ||
| * @param staticAToken address of the staticAToken for which rewards have been refreshed. | ||
| */ | ||
| event RefreshSucceeded(address indexed staticAToken); | ||
|
|
||
| /** | ||
| * @notice method to check if the staticAToken is disabled for automation. | ||
| * @param staticAToken staticAToken to check if disabled for refresh rewards automation. | ||
| **/ | ||
| function isDisabled(address staticAToken) external view returns (bool); | ||
|
|
||
| /** | ||
| * @notice method to disable automation for the staticAToken. | ||
| * @param staticAToken staticAToken to disable automation for refresh rewards. | ||
| * @param disable bool true to disable automation, false to enable it back. | ||
| **/ | ||
| function disableAutomation(address staticAToken, bool disable) external; | ||
|
|
||
| /** | ||
| * @notice method to get the maximum number of actions that can be performed by the robot in one performUpkeep. | ||
| * @return max number of actions. | ||
| */ | ||
| function MAX_ACTIONS() external returns (uint256); | ||
|
|
||
| /** | ||
| * @notice method to get the rewards controller of the protocol. | ||
| * @return address of the aave rewards controller. | ||
| */ | ||
| function REWARDS_CONTROLLER() external returns (IRewardsController); | ||
|
|
||
| /** | ||
| * @notice method to get the static a token factory. | ||
| * @return address of the static a token factory contract. | ||
| */ | ||
| function STATIC_A_TOKEN_FACTORY() external returns (IStaticATokenFactory); | ||
| } |
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,41 @@ | ||
| // SPDX-License-Identifier: BUSL-1.1 | ||
| pragma solidity ^0.8.0; | ||
|
|
||
| import {AutomationCompatibleInterface} from 'chainlink/src/v0.8/interfaces/automation/AutomationCompatibleInterface.sol'; | ||
| import {GasCappedRobotBase} from 'aave-governance-v3-robot/contracts/gasprice-capped-robots/GasCappedRobotBase.sol'; | ||
| import {RefreshRewardsRobot} from './RefreshRewardsRobot.sol'; | ||
|
|
||
| /** | ||
| * @title GasCappedRefreshRewardsRobot | ||
| * @author BGD Labs | ||
| * @notice Automation contract to automate refresh rewards for staticATokens if a reward | ||
| * is added after staticAToken creation to register the missing rewards. | ||
| * The difference from RefreshRewardsRobot is that automation is only | ||
| * performed when the network gas price in within the maximum configured range. | ||
| */ | ||
| contract GasCappedRefreshRewardsRobot is RefreshRewardsRobot, GasCappedRobotBase { | ||
| /** | ||
| * @param staticATokenFactory address of the static a token factory contract. | ||
| * @param rewardsController address of the rewards controller of the protocol. | ||
| * @param gasPriceOracle address of the gas price oracle contract. | ||
| */ | ||
| constructor( | ||
| address staticATokenFactory, | ||
| address rewardsController, | ||
| address gasPriceOracle | ||
| ) | ||
| RefreshRewardsRobot(staticATokenFactory, rewardsController) | ||
| GasCappedRobotBase(gasPriceOracle) | ||
| {} | ||
|
|
||
| /** | ||
| * @inheritdoc AutomationCompatibleInterface | ||
| * @dev runs off-chain, checks if there is a reward added after statToken creation which needs to be registered. | ||
| * also checks that the gas price of the network in within range to perform actions. | ||
| */ | ||
| function checkUpkeep(bytes memory) public view virtual override returns (bool, bytes memory) { | ||
| if (!isGasPriceInRange()) return (false, ''); | ||
|
|
||
| return super.checkUpkeep(''); | ||
| } | ||
| } |
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,40 @@ | ||
| // SPDX-License-Identifier: BUSL-1.1 | ||
| pragma solidity ^0.8.0; | ||
|
|
||
| import {GasCappedRefreshRewardsRobot} from './GasCappedRefreshRewardsRobot.sol'; | ||
| import {IGasPriceCappedRobot} from 'aave-governance-v3-robot/interfaces/IGasPriceCappedRobot.sol'; | ||
|
|
||
| /** | ||
| * @title GelatoGasCappedRefreshRewardsRobot | ||
| * @author BGD Labs | ||
| * @notice Automation contract to automate refresh rewards for staticATokens. | ||
| * The difference from GasCappedRefreshRewardsRobot is that we use tx.gasprice | ||
| * instead of gas price oracle in order to limit the execution of the robot. | ||
| */ | ||
| contract GelatoGasCappedRefreshRewardsRobot is GasCappedRefreshRewardsRobot { | ||
| /** | ||
| * @param staticATokenFactory address of the static a token factory contract. | ||
| * @param rewardsController address of the rewards controller of the protocol. | ||
| */ | ||
| constructor( | ||
| address staticATokenFactory, | ||
| address rewardsController | ||
| ) GasCappedRefreshRewardsRobot(staticATokenFactory, rewardsController, address(0)) {} | ||
|
|
||
| /** | ||
| * @inheritdoc GasCappedRefreshRewardsRobot | ||
| * @dev the returned bytes is specific to gelato and is encoded with the function selector. | ||
| */ | ||
| function checkUpkeep(bytes memory) public view override returns (bool, bytes memory) { | ||
| (bool upkeepNeeded, bytes memory encodedPayloadIdsToExecute) = super.checkUpkeep(''); | ||
| return (upkeepNeeded, abi.encodeCall(this.performUpkeep, encodedPayloadIdsToExecute)); | ||
| } | ||
|
|
||
| /// @inheritdoc IGasPriceCappedRobot | ||
| function isGasPriceInRange() public view virtual override returns (bool) { | ||
| if (tx.gasprice > _maxGasPrice) { | ||
| return false; | ||
| } | ||
| return true; | ||
| } | ||
| } |
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,97 @@ | ||
| // SPDX-License-Identifier: BUSL-1.1 | ||
| pragma solidity ^0.8.0; | ||
|
|
||
| import {IStaticATokenFactory} from '../interfaces/IStaticATokenFactory.sol'; | ||
| import {IRefreshRewardsRobot, AutomationCompatibleInterface} from '../interfaces/IRefreshRewardsRobot.sol'; | ||
| import {IStaticATokenLM} from '../interfaces/IStaticATokenLM.sol'; | ||
| import {IRewardsController} from 'aave-v3-periphery/contracts/rewards/interfaces/IRewardsController.sol'; | ||
| import {Ownable} from 'solidity-utils/contracts/oz-common/Ownable.sol'; | ||
|
|
||
| /** | ||
| * @title RefreshRewardsRobot | ||
| * @author BGD Labs | ||
| * @notice Automation contract to automate refresh rewards for staticATokens if a reward | ||
| * is added after staticAToken creation to register the missing rewards. | ||
| */ | ||
| contract RefreshRewardsRobot is Ownable, IRefreshRewardsRobot { | ||
| mapping(address => bool) internal disabledStaticATokens; | ||
|
|
||
| /// @inheritdoc IRefreshRewardsRobot | ||
| IStaticATokenFactory public immutable STATIC_A_TOKEN_FACTORY; | ||
|
|
||
| /// @inheritdoc IRefreshRewardsRobot | ||
| IRewardsController public immutable REWARDS_CONTROLLER; | ||
|
|
||
| /// @inheritdoc IRefreshRewardsRobot | ||
| uint256 public constant MAX_ACTIONS = 10; | ||
|
|
||
| /** | ||
| * @param staticATokenFactory address of the static a token factory contract. | ||
| * @param rewardsController address of the rewards controller of the protocol. | ||
| */ | ||
| constructor(address staticATokenFactory, address rewardsController) { | ||
| STATIC_A_TOKEN_FACTORY = IStaticATokenFactory(staticATokenFactory); | ||
| REWARDS_CONTROLLER = IRewardsController(rewardsController); | ||
| } | ||
|
|
||
| /** | ||
| * @inheritdoc AutomationCompatibleInterface | ||
| * @dev runs off-chain, checks if there is a reward added after statToken creation which needs to be registered. | ||
| */ | ||
| function checkUpkeep(bytes memory) public view virtual override returns (bool, bytes memory) { | ||
| address[] memory staticATokensToRefresh = new address[](MAX_ACTIONS); | ||
| address[] memory staticATokens = STATIC_A_TOKEN_FACTORY.getStaticATokens(); | ||
| uint256 actionsCount = 0; | ||
|
|
||
| for (uint i = 0; i < staticATokens.length; i++) { | ||
| address aTokenAddress = address(IStaticATokenLM(staticATokens[i]).aToken()); | ||
| address[] memory rewards = REWARDS_CONTROLLER.getRewardsByAsset(aTokenAddress); | ||
|
|
||
| for (uint256 j = 0; j < rewards.length; j++) { | ||
| bool isRegisteredReward = IStaticATokenLM(staticATokens[i]).isRegisteredRewardToken( | ||
| rewards[j] | ||
| ); | ||
| if (!isRegisteredReward && actionsCount < MAX_ACTIONS && !isDisabled(staticATokens[i])) { | ||
| staticATokensToRefresh[actionsCount] = staticATokens[i]; | ||
| actionsCount++; | ||
|
brotherlymite marked this conversation as resolved.
|
||
| break; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if (actionsCount > 0) { | ||
| // we do not know the length in advance, so we init arrays with MAX_ACTIONS | ||
| // and then squeeze the array using mstore | ||
| assembly { | ||
| mstore(staticATokensToRefresh, actionsCount) | ||
| } | ||
| bytes memory performData = abi.encode(staticATokensToRefresh); | ||
| return (true, performData); | ||
| } | ||
|
|
||
| return (false, ''); | ||
| } | ||
|
|
||
| /** | ||
| * @dev executes refreshRewardTokens() on the staticAToken to register the missing rewards | ||
| * @param performData array of staticATokens for which refresh needs to be performed | ||
| */ | ||
| function performUpkeep(bytes calldata performData) external override { | ||
| address[] memory staticATokensToRefresh = abi.decode(performData, (address[])); | ||
|
|
||
| for (uint256 i = 0; i < staticATokensToRefresh.length; i++) { | ||
| IStaticATokenLM(staticATokensToRefresh[i]).refreshRewardTokens(); | ||
| emit RefreshSucceeded(staticATokensToRefresh[i]); | ||
| } | ||
| } | ||
|
|
||
| /// @inheritdoc IRefreshRewardsRobot | ||
| function isDisabled(address staticAToken) public view returns (bool) { | ||
| return disabledStaticATokens[staticAToken]; | ||
| } | ||
|
|
||
| /// @inheritdoc IRefreshRewardsRobot | ||
| function disableAutomation(address staticAToken, bool disable) external onlyOwner { | ||
| disabledStaticATokens[staticAToken] = disable; | ||
| } | ||
| } | ||
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.