Skip to content
This repository was archived by the owner on Nov 1, 2023. It is now read-only.
Open
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
5f18360
feat: strategic assets
efecarranza Jun 29, 2023
a530d0e
chore; start test
efecarranza Jun 29, 2023
361e4a6
feat: add tests SD tokens
efecarranza Jul 5, 2023
ab4ae69
feat: add vote gauge weight test
efecarranza Jul 5, 2023
c4a9662
Merge remote-tracking branch 'origin/main' into assets
efecarranza Jul 10, 2023
675e5b6
feat: add lsd tests
efecarranza Jul 11, 2023
b4b5461
chore: remove unused functions
efecarranza Jul 12, 2023
7bfb8e0
feat: add missing locking tests
efecarranza Jul 13, 2023
2da6f22
chore: updates of solidity version
efecarranza Jul 18, 2023
d5c4655
feat: use Ownable.sol and Initializable.sol
efecarranza Jul 18, 2023
2f4bfbe
feat: initialize with ownership
efecarranza Jul 19, 2023
4a1cb29
feat: include README with function signatures
efecarranza Jul 19, 2023
367e493
Merge remote-tracking branch 'origin/main' into assets
efecarranza Jul 20, 2023
52cfba1
feat: switch to using ownable with guardian
efecarranza Jul 20, 2023
8256d0c
chore use approve
efecarranza Jul 24, 2023
9f6e90e
feat: simplify to handle only veBAL
efecarranza Aug 18, 2023
3e5d590
chore: updates per PR review
efecarranza Aug 31, 2023
32ef455
chore: add missing natspecs
efecarranza Sep 1, 2023
f5001ce
chore: add missing event and fix natspec
efecarranza Sep 4, 2023
479e34a
Merge remote-tracking branch 'origin/main' into assets
efecarranza Sep 16, 2023
45d878d
feat: add VLAURA support
efecarranza Sep 16, 2023
136bed8
Merge remote-tracking branch 'origin/main' into assets
efecarranza Sep 16, 2023
b8c6a40
feat: add tests for vlaura
efecarranza Sep 18, 2023
512866e
feat: update natspec
efecarranza Sep 25, 2023
bda519c
feat: update README to include vlAURA functions
efecarranza Sep 25, 2023
2eaa696
chore: remove mention of sd token
efecarranza Sep 25, 2023
3d58410
feat: update PR with natspec and revert
efecarranza Sep 28, 2023
a4acaa6
feat: update guardian address
efecarranza Oct 2, 2023
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
14 changes: 14 additions & 0 deletions src/AaveV3StrategicAssets_20230606/Common.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

import {OwnableWithGuardian} from 'solidity-utils/contracts/access-control/OwnableWithGuardian.sol';

// @author Llama
abstract contract Common is OwnableWithGuardian {
/// @notice Provided address is zero address
error InvalidZeroAddress();

/// @notice One week, in seconds. Vote-locking is rounded down to weeks.
uint256 internal constant WEEK = 7 days;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {GovHelpers} from 'aave-helpers/GovHelpers.sol';
import {EthereumScript} from 'aave-helpers/ScriptUtils.sol';

import {StrategicAssetsManagerPayload} from './StrategicAssetsManagerPayload.sol';

contract DeployAssetManagementContracts is EthereumScript {
function run() external broadcast {
new StrategicAssetsManagerPayload();
}
}

contract DeployStrategicAssetsProposal is EthereumScript {
Copy link
Contributor

Choose a reason for hiding this comment

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

@efecarranza I think we don't need a proposal for this no, as we can just deploy and set the permission of the asset manager to the executor and guardian and that's it... or am I missing something.

function run() external broadcast {
GovHelpers.Payload[] memory payloads = new GovHelpers.Payload[](1);
payloads[0] = GovHelpers.buildMainnet(address(0));
GovHelpers.createProposal(
payloads,
GovHelpers.ipfsHashFile(
vm,
'src/AaveV3StrategicAssets_20230606/AIP-STRATEGIC-ASSETS-MANAGER.md'
)
);
}
}
41 changes: 41 additions & 0 deletions src/AaveV3StrategicAssets_20230606/LSDLiquidityGaugeManager.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

import {ILiquidityGaugeController} from './interfaces/ILiquidityGaugeController.sol';
import {Common} from './Common.sol';

// @author Llama
abstract contract LSDLiquidityGaugeManager is Common {
event GaugeControllerChanged(address indexed oldController, address indexed newController);
event GaugeVote(address indexed gauge, uint256 amount);

/// @notice Setting to the same controller address as currently set.
error SameController();

/// @notice Address of LSD Gauge Controller
address public gaugeControllerBalancer;

/// @notice Set the gauge controller used for gauge weight voting
/// @param _gaugeController The gauge controller address
function setGaugeController(address _gaugeController) public onlyOwnerOrGuardian {
if (_gaugeController == address(0)) revert InvalidZeroAddress();

address oldController = gaugeControllerBalancer;
if (oldController == _gaugeController) revert SameController();

gaugeControllerBalancer = _gaugeController;

emit GaugeControllerChanged(oldController, gaugeControllerBalancer);
}

/// @notice Vote for a gauge's weight
/// @param gauge the address of the gauge to vote for
/// @param weight the weight of gaugeAddress in basis points [0, 10.000]
function voteForGaugeWeight(address gauge, uint256 weight) external onlyOwnerOrGuardian {
if (gauge == address(0)) revert InvalidZeroAddress();

ILiquidityGaugeController(gaugeControllerBalancer).vote_for_gauge_weights(gauge, weight);
emit GaugeVote(gauge, weight);
}
}
171 changes: 171 additions & 0 deletions src/AaveV3StrategicAssets_20230606/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
# Streategic Assets Manager Information

## Main Idea

The main idea of this new contract is for the DAO to be able to handle strategic assets (assets that will earn rewards, or assets that can be used to
vote on different protocols, for example) without the need to go through the full governance flow every week.

Take the following example: the DAO wants to hold veBAL as part of its strategy for the long term. To get the most out of veBAL, the DAO should "re-lock"
its holdings on a weekly basis. This is a very tedious approach and can lead to a lot of voter fatigue. Because of this, the StrategicAssetsManager contract
can be governed by the DAO, while some functions can be invoked by an allowed guardian, acting on the role of Asset Manager.

## Functionality

#### StrategicAssetsManager.sol

`function withdrawERC20(address token, address to, uint256 amount) external onlyOwner`

Sends ERC20 tokens to an address. Withdrawal mechanism.

`function updateGuardian(address _manager) external onlyOwner`

Updates guardian role, which in this contract functions as a strategic asset manager. Inherited from OwnableWithGuardian.

`function transferOwnership(address _owner) external onlyOwner`

Updates the owner of the contract. Inherited from Ownable.

#### VeTokenManager.sol

```
function buyBoost(
address underlying,
address delegator,
address receiver,
uint256 amount,
uint256 duration
) external onlyOwnerOrManager
```

Purchase boost to incentivize rewards earned by locking (up to 2.5x of earnings). Spend fee token.
For more info see: https://doc.paladin.vote/warden-boost/boost-market

The idea is to increase the yield in the provided liquidity.
For example, pay 10 BAL to boost rewards in a veBAL pool up to 2.5x times, to earn more BAL in return.

```
function sellBoost(
address underlying,
uint256 pricePerVote,
uint64 maxDuration,
uint64 expiryTime,
uint16 minPerc,
uint16 maxPerc,
bool useAdvicePrice
) external onlyOwnerOrManager
```

Owner of veToken allows others to incentivize their liquidity pools by selling boost. The price can be chosen by the user, or by setting useAdvicePrice, let Warden determine the price.
The seller of boost receives the native token.

```
function updateBoostOffer(
address underlying,
uint256 pricePerVote,
uint64 maxDuration,
uint64 expiryTime,
uint16 minPerc,
uint16 maxPerc,
bool useAdvicePrice
) external onlyOwnerOrManager
```

Allows the user to update an existing offer to sell boost.

`function removeBoostOffer(address underlying) external onlyOwnerOrManager`

Removes a boost offer.

` function claimBoostRewards(address underlying) external onlyOwnerOrManager`

Claim rewards earned by selling boost.

`function setSpaceIdVEBAL(address underlying, bytes32 _spaceId) external onlyOwnerOrManager`

Sets the spaceID that's used by protocol on Snapshot for voting. For example, "balancer.eth" is Balancer's spaceId on Snapshot.

```
function setDelegateVEBAL(
address underlying,
address newDelegate
) external onlyOwnerOrManager
```

Delegate tokens so they can vote on Snapshot.

`function clearDelegateVEBAL(address underlying) external onlyOwnerOrManager`

Remove the active delegate.

```
function setLockDurationVEBAL(
address underlying,
uint256 newLockDuration
) external onlyOwnerOrManager
```

Set the lock duration to specific time. For example, max lock for veBAL is 1 year, so set to 1 year (or less).

`function lockVEBAL(address underlying) external onlyOwnerOrManager`

The main function for veBAL.
Initially, it locks the B-80BAL-20WETH token to receive veBAL. (This contract needs to be allow-listed by Balancer prior to calling or it will fail).
On subsequent calls (for example, weekly) it extends the lock duration once again. The voting % available per token is dependent on the locking duration.
If locking duration is 6 months and the maximum duration is 1 year, then the voting weight is only half.
This function also locks more of the native token held by StrategicAssetsManager available on the contract.

`function unlockVEBAL(address underlying) external onlyOwnerOrManager`

Unlocks the veToken in order to receive the underlying once again. Lock duration needs to have passed or transaction will revert.

#### VlTokenManager.sol

`function lockVLAURA(uint256 amount) external onlyOwnerOrGuardian`

Locks AURA into vlAURA (if not locked before).

`function claimVLAURARewards() external onlyOwnerOrGuardian`

Claims rewards accrued by locking vlAURA.

`function delegateVLAURA(address delegatee) external onlyOwnerOrGuardian`

Delegates vlAURA for voting purposes.

`function relockVLAURA() external onlyOwnerOrGuardian`

Relocks vlAURA that has been previously locked.

`function unlockVLAURA() external onlyOwnerOrGuardian`

Unlock vlAURA position into AURA. Lock period needs to have passed or it will revert.

`function emergencyWithdrawVLAURA() external onlyOwnerOrGuardian`

Emergency function to exit a position if the AURA system is shut down.

##### LSDLiquidityGaugeManager.sol

`function setGaugeController(address token, address gaugeController) public onlyOwnerOrManager`

Sets the address that handles gauges for veTokens.

Here is the proposal on Balancer as it relates to GHO: https://forum.balancer.fi/t/bip-xxx-approve-the-smbpt-gauges-for-the-aave-sm/4949
This post has the explanation on all the steps the DAO can expect to interact with these protocols to maximize rewards.
The excalidraw towards the bottom of the page is helpful in seeing the full flow.

Curve docs on liquidity gauges: https://curve.readthedocs.io/dao-gauges.html

The main concept here is that the ecosystem rewards liquidity providers by rewarding them with token emissions. These tokens are distributed according to which gauges receive the
most votes.

```
function voteForGaugeWeight(
address token,
address gauge,
uint256 weight
) external onlyOwnerOrManager
```

Utilizing the veToken holdings, the DAO can vote to redirect emissions to the DAO's own gauge.
Here, by voting for the DAO's gauge, and also purchasing boost, the DAO can expect to earn a lot more BAL rewards over time than just by holding a veToken for example.
42 changes: 42 additions & 0 deletions src/AaveV3StrategicAssets_20230606/StrategicAssetsManager.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

import {IERC20} from 'solidity-utils/contracts/oz-common/interfaces/IERC20.sol';
import {Initializable} from 'solidity-utils/contracts/transparent-proxy/Initializable.sol';
import {SafeERC20} from 'solidity-utils/contracts/oz-common/SafeERC20.sol';
import {AaveGovernanceV2} from 'aave-address-book/AaveGovernanceV2.sol';
import {AaveV3Ethereum} from 'aave-address-book/AaveV3Ethereum.sol';

import {LSDLiquidityGaugeManager} from './LSDLiquidityGaugeManager.sol';
import {VeTokenManager} from './VeTokenManager.sol';
import {VlTokenManager} from './VlTokenManager.sol';

// @author Llama
contract StrategicAssetsManager is
Initializable,
LSDLiquidityGaugeManager,
VeTokenManager,
VlTokenManager
{
using SafeERC20 for IERC20;

event WithdrawalERC20(address indexed _token, uint256 _amount);

// @notice Initialize function
function initialize() external initializer {
_transferOwnership(AaveGovernanceV2.SHORT_EXECUTOR);
_updateGuardian(AaveGovernanceV2.SHORT_EXECUTOR);
spaceIdBalancer = 'balancer.eth';
gaugeControllerBalancer = 0xC128468b7Ce63eA702C1f104D55A2566b13D3ABD;
lockDurationVEBAL = 365 days;
}

// @notice Withdraw a specified amount of ERC20 token to the Aave Collector
// @param token The address of the ERC20 token to withdraw
// @param amount The amount of token to withdraw
Copy link
Contributor

Choose a reason for hiding this comment

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

Hey, multi-line comments are normally done either with "///" or "/** */" (as we do in our code), just something to note.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oh wow, i don't know why i went with // only. thanks for catching!

function withdrawERC20(address token, uint256 amount) external onlyOwner {
IERC20(token).safeTransfer(address(AaveV3Ethereum.COLLECTOR), amount);
emit WithdrawalERC20(token, amount);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

import {AaveMisc} from 'aave-address-book/AaveMisc.sol';
import {IProposalGenericExecutor} from 'aave-helpers/interfaces/IProposalGenericExecutor.sol';
import {TransparentProxyFactory} from 'solidity-utils/contracts/transparent-proxy/TransparentProxyFactory.sol';

import {StrategicAssetsManager} from './StrategicAssetsManager.sol';

contract StrategicAssetsManagerPayload is IProposalGenericExecutor {
function execute() external {
address strategicAssetsManager = address(new StrategicAssetsManager());
TransparentProxyFactory(AaveMisc.TRANSPARENT_PROXY_FACTORY_ETHEREUM).create(
strategicAssetsManager,
AaveMisc.PROXY_ADMIN_ETHEREUM,
abi.encodeWithSelector(StrategicAssetsManager.initialize.selector)
);
}
}
Loading