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 10 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
24 changes: 24 additions & 0 deletions src/AaveV3StrategicAssets_20230606/Core.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

import {Ownable} from 'solidity-utils/contracts/oz-common/Ownable.sol';

abstract contract Core is Ownable {
/// @notice Provided address is 0x address
error Invalid0xAddress();
/// @notice Not authorized
error InvalidCaller();
/// @notice Token not registered as valid in the system
error TokenNotRegistered();

modifier onlyOwnerOrManager() {
if (msg.sender != owner() && msg.sender != manager) revert InvalidCaller();
_;
}

/// @notice One week, in seconds. Vote-locking is rounded down to weeks.
uint256 internal constant WEEK = 7 days;

address public manager;
}
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'
)
);
}
}
51 changes: 51 additions & 0 deletions src/AaveV3StrategicAssets_20230606/LSDLiquidityGaugeManager.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

import {IERC20} from 'solidity-utils/contracts/oz-common/interfaces/IERC20.sol';

import {ILiquidityGauge, ILiquidityGaugeController} from './interfaces/ILiquidityGauge.sol';
import {Core} from './Core.sol';

abstract contract LSDLiquidityGaugeManager is Core {
event GaugeControllerChanged(address indexed oldController, address indexed newController);
event GaugeRewardsClaimed(address indexed gauge, address indexed token, uint256 amount);
event GaugeStake(address indexed gauge, uint256 amount);
event GaugeUnstake(address indexed gauge, uint256 amount);
event GaugeVote(address indexed gauge, uint256 amount);

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

/// @notice Address of LSD to address of gauge controller mapping
mapping(address tokenAddress => address) public gaugeControllers;

/// @notice Set the gauge controller used for gauge weight voting
/// @param token Address of the LSD token
/// @param gaugeController The gauge controller address
function setGaugeController(address token, address gaugeController) public onlyOwnerOrManager {
if (gaugeController == address(0)) revert Invalid0xAddress();

address oldController = gaugeControllers[token];
if (oldController == gaugeController) revert SameController();

gaugeControllers[token] = gaugeController;

emit GaugeControllerChanged(oldController, gaugeController);
}

/// @notice Vote for a gauge's weight
/// @param token the address of the token to vote for
/// @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 token,
address gauge,
uint256 weight
) external onlyOwnerOrManager {
if (gauge == address(0)) revert Invalid0xAddress();

ILiquidityGaugeController(gaugeControllers[token]).vote_for_gauge_weights(gauge, weight);
emit GaugeVote(gauge, weight);
}
}
35 changes: 35 additions & 0 deletions src/AaveV3StrategicAssets_20230606/SdTokenManager.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

import {IERC20} from 'solidity-utils/contracts/oz-common/interfaces/IERC20.sol';
import {SafeERC20} from 'solidity-utils/contracts/oz-common/SafeERC20.sol';

import {ISdDepositor} from './interfaces/ISdDepositor.sol';
import {Core} from './Core.sol';

abstract contract SdTokenManager is Core {
using SafeERC20 for IERC20;

struct SdToken {
/// @notice Address of SD Token
address sdToken;
/// @notice Address of locker to deposit SD Token
address depositor;
}

mapping(address => SdToken) public sdTokens;

/// @notice Locks underlying token into sdToken and stakes into gauge
function lock(address underlying, uint256 amount) external onlyOwnerOrManager {
SdToken storage token = sdTokens[underlying];
if (token.sdToken == address(0)) revert Invalid0xAddress();

IERC20(underlying).approve(token.depositor, amount);
ISdDepositor(token.depositor).deposit(amount, true, true, address(this));
}

function retrieveUnderlying(address token) external onlyOwnerOrManager {
// TODO: Add curve swap? or COW? Nothing?
}
}
107 changes: 107 additions & 0 deletions src/AaveV3StrategicAssets_20230606/StrategicAssetsManager.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

import {IERC20} from 'solidity-utils/contracts/oz-common/interfaces/IERC20.sol';
import {Ownable} from 'solidity-utils/contracts/oz-common/Ownable.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 {SdTokenManager} from './SdTokenManager.sol';
import {LSDLiquidityGaugeManager} from './LSDLiquidityGaugeManager.sol';
import {VeTokenManager} from './VeTokenManager.sol';

contract StrategicAssetsManager is
Initializable,
LSDLiquidityGaugeManager,
VeTokenManager,
SdTokenManager
{
using SafeERC20 for IERC20;

event SdTokenAdded(address indexed underlying, address sdToken);
event SdTokenRemoved(address indexed underlying, address sdToken);
event StrategicAssetsManagerChanged(address indexed oldManager, address indexed newManager);
event VeTokenAdded(address indexed underlying, address veToken);
event VeTokenRemoved(address indexed underlying, address veToken);
event WithdrawalERC20(address indexed _token, address _to, uint256 _amount);

constructor() Ownable() {}

function initialize() external initializer {}

function addVeToken(
address underlying,
address veToken,
address warden,
uint256 lockDuration,
address initialDelegate,
bytes32 spaceId
) external onlyOwner {
if (underlying == address(0) || veToken == address(0)) revert Invalid0xAddress();

VeToken memory newToken;
newToken.veToken = veToken;
newToken.warden = warden;
newToken.lockDuration = lockDuration;
newToken.delegate = initialDelegate;
newToken.spaceId = spaceId;

veTokens[underlying] = newToken;

if (initialDelegate != address(0)) {
_delegate(veTokens[underlying], initialDelegate);
}

if (spaceId != '') {
_setSpaceId(veTokens[underlying], spaceId);
}

emit VeTokenAdded(underlying, veToken);
}

function removeVeToken(address underlying) external onlyOwner {
address veToken = veTokens[underlying].veToken;
delete veTokens[underlying];
emit VeTokenRemoved(underlying, veToken);
}

function addSdToken(address underlying, address sdToken, address depositor) external onlyOwner {
if (underlying == address(0) || sdToken == address(0) || depositor == address(0))
revert Invalid0xAddress();

SdToken memory newToken;
newToken.sdToken = sdToken;
newToken.depositor = depositor;

sdTokens[underlying] = newToken;
emit SdTokenAdded(underlying, sdToken);
}

function removeSdToken(address underlying) external onlyOwner {
address sdToken = sdTokens[underlying].sdToken;
delete sdTokens[underlying];
emit SdTokenRemoved(underlying, sdToken);
}

function withdrawERC20(address token, address to, uint256 amount) external onlyOwner {
if (to == address(0)) revert Invalid0xAddress();

IERC20(token).safeTransfer(to, amount);
emit WithdrawalERC20(token, to, amount);
}

function setStrategicAssetsManager(address _manager) external onlyOwner {
if (_manager == address(0)) revert Invalid0xAddress();
address oldManager = manager;
manager = _manager;
emit StrategicAssetsManagerChanged(oldManager, manager);
}

function removeStrategicAssetManager() external onlyOwner {
address oldManager = manager;
manager = address(0);
emit StrategicAssetsManagerChanged(oldManager, manager);
}
}
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,
''
);
}
}
128 changes: 128 additions & 0 deletions src/AaveV3StrategicAssets_20230606/TestLSDLiquidityGaugeManager.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

import {Test} from 'forge-std/Test.sol';
import {AaveGovernanceV2} from 'aave-address-book/AaveGovernanceV2.sol';
import {IERC20} from 'solidity-utils/contracts/oz-common/interfaces/IERC20.sol';

import {ILiquidityGaugeController} from './interfaces/ILiquidityGauge.sol';
import {LSDLiquidityGaugeManager} from './LSDLiquidityGaugeManager.sol';
import {StrategicAssetsManager} from './StrategicAssetsManager.sol';
import {Core} from './Core.sol';

interface ISmartWalletChecker {
function allowlistAddress(address contractAddress) external;
}

contract LSDLiquidityGaugeManagerTest is Test {
event GaugeControllerChanged(address indexed oldController, address indexed newController);
event GaugeRewardsClaimed(address indexed gauge, address indexed token, uint256 amount);
event GaugeStake(address indexed gauge, uint256 amount);
event GaugeUnstake(address indexed gauge, uint256 amount);
event GaugeVote(address indexed gauge, uint256 amount);

// Helpers
address public constant SMART_WALLET_CHECKER = 0x7869296Efd0a76872fEE62A058C8fBca5c1c826C;

address public constant B_80BAL_20WETH = 0x5c6Ee304399DBdB9C8Ef030aB642B10820DB8F56;
address public constant VE_BAL = 0xC128a9954e6c874eA3d62ce62B468bA073093F25;
address public constant VE_BAL_GAUGE = 0x27Fd581E9D0b2690C2f808cd40f7fe667714b575; // TODO: Replace with right gauge
address public constant WARDEN_VE_BAL = 0x42227bc7D65511a357c43993883c7cef53B25de9;
address public constant BALANCER_GAUGE_CONTROLLER = 0xC128468b7Ce63eA702C1f104D55A2566b13D3ABD;
address public constant STAKE_DAO_GAUGE_CONTROLLER = 0x75f8f7fa4b6DA6De9F4fE972c811b778cefce882;

StrategicAssetsManager public strategicAssets;

function setUp() public {
vm.createSelectFork(vm.rpcUrl('mainnet'), 17523941);

vm.startPrank(AaveGovernanceV2.SHORT_EXECUTOR);
strategicAssets = new StrategicAssetsManager();
vm.stopPrank();
}

function _addVeToken(
address underlying,
address veToken,
address warden,
uint256 lockDuration,
address delegate,
bytes32 spaceId
) internal {
vm.startPrank(AaveGovernanceV2.SHORT_EXECUTOR);
strategicAssets.addVeToken(underlying, veToken, warden, lockDuration, delegate, spaceId);
vm.stopPrank();
}
}

contract SetGaugeController is LSDLiquidityGaugeManagerTest {
function test_revertsIf_invalidCaller() public {
vm.expectRevert(Core.InvalidCaller.selector);
strategicAssets.setGaugeController(B_80BAL_20WETH, BALANCER_GAUGE_CONTROLLER);
}

function test_revertsIf_invalid0xAddress() public {
vm.expectRevert(Core.Invalid0xAddress.selector);
vm.startPrank(AaveGovernanceV2.SHORT_EXECUTOR);
strategicAssets.setGaugeController(B_80BAL_20WETH, address(0));
vm.stopPrank();
}

function test_revertsIf_settingToSameController() public {
vm.startPrank(AaveGovernanceV2.SHORT_EXECUTOR);
strategicAssets.setGaugeController(B_80BAL_20WETH, BALANCER_GAUGE_CONTROLLER);

vm.expectRevert(LSDLiquidityGaugeManager.SameController.selector);
strategicAssets.setGaugeController(B_80BAL_20WETH, BALANCER_GAUGE_CONTROLLER);
vm.stopPrank();
}

function test_successful() public {
vm.expectEmit();
emit GaugeControllerChanged(address(0), BALANCER_GAUGE_CONTROLLER);

vm.startPrank(AaveGovernanceV2.SHORT_EXECUTOR);
strategicAssets.setGaugeController(B_80BAL_20WETH, BALANCER_GAUGE_CONTROLLER);
vm.stopPrank();

assertEq(strategicAssets.gaugeControllers(B_80BAL_20WETH), BALANCER_GAUGE_CONTROLLER);
}
}

contract VoteForGaugeWeight is LSDLiquidityGaugeManagerTest {
function test_revertsIf_invalidCaller() public {
vm.expectRevert(Core.InvalidCaller.selector);
strategicAssets.voteForGaugeWeight(B_80BAL_20WETH, VE_BAL_GAUGE, 100);
}

function test_revertsIf_gaugeIsZeroAddress() public {
vm.expectRevert(Core.Invalid0xAddress.selector);
vm.startPrank(AaveGovernanceV2.SHORT_EXECUTOR);
strategicAssets.voteForGaugeWeight(B_80BAL_20WETH, address(0), 100);
vm.stopPrank();
}

function test_successful() public {
_addVeToken(B_80BAL_20WETH, VE_BAL, WARDEN_VE_BAL, 365 * 86400, address(0), '');

vm.startPrank(0x10A19e7eE7d7F8a52822f6817de8ea18204F2e4f); // Authenticated Address
ISmartWalletChecker(SMART_WALLET_CHECKER).allowlistAddress(address(strategicAssets));
vm.stopPrank();

deal(B_80BAL_20WETH, address(strategicAssets), 1_000e18);

uint256 weightBefore = ILiquidityGaugeController(BALANCER_GAUGE_CONTROLLER).get_gauge_weight(VE_BAL_GAUGE);

vm.startPrank(AaveGovernanceV2.SHORT_EXECUTOR);
strategicAssets.lock(B_80BAL_20WETH);
strategicAssets.setGaugeController(B_80BAL_20WETH, BALANCER_GAUGE_CONTROLLER);

vm.expectEmit();
emit GaugeVote(VE_BAL_GAUGE, 100);
strategicAssets.voteForGaugeWeight(B_80BAL_20WETH, VE_BAL_GAUGE, 100);
vm.stopPrank();

assertGt(ILiquidityGaugeController(BALANCER_GAUGE_CONTROLLER).get_gauge_weight(VE_BAL_GAUGE), weightBefore);
}
}
Loading