-
Notifications
You must be signed in to change notification settings - Fork 3
16slim/ve angle #6
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
base: master
Are you sure you want to change the base?
Changes from 26 commits
fe6844d
7a07c62
0be3adf
e35f803
c863f46
3676450
64c75d7
649c309
f189c10
f389c7e
46571e0
d0140f0
a9ff880
4096176
31ff372
9db840c
88f18fd
618637b
15ce260
de4174d
5072669
06b0dae
aa72c39
2435759
a6aa5d6
cdbe6c4
915b8a4
dfc900d
16f7e42
432873a
8c607ed
ada3a34
8fb4d14
d7571d9
f4e0c73
8c63ff1
528a4c4
8ba167e
c848384
542f347
748466f
5c3038e
ea48325
cf81296
6937fad
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,175 @@ | ||
| // SPDX-License-Identifier: AGPL-3.0 | ||
| pragma solidity ^0.8.12; | ||
| pragma experimental ABIEncoderV2; | ||
|
|
||
| import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
| import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; | ||
| import {Address} from "@openzeppelin/contracts/utils/Address.sol"; | ||
|
|
||
| import {YearnAngleVoter} from "./YearnAngleVoter.sol"; | ||
|
|
||
| import "./interfaces/curve/ICurve.sol"; | ||
| import "./interfaces/Angle/IStableMaster.sol"; | ||
| import "./interfaces/Angle/IAngleGauge.sol"; | ||
| import "./interfaces/Uniswap/IUniV2.sol"; | ||
|
|
||
| library SafeVoter { | ||
| function safeExecute( | ||
| YearnAngleVoter voter, | ||
| address to, | ||
| uint256 value, | ||
| bytes memory data | ||
| ) internal { | ||
| (bool success, ) = voter.execute(to, value, data); | ||
| require(success); | ||
| } | ||
| } | ||
|
|
||
| contract AngleStrategyVoterProxy { | ||
| using SafeVoter for YearnAngleVoter; | ||
| using SafeERC20 for IERC20; | ||
| using Address for address; | ||
|
|
||
| YearnAngleVoter public yearnAngleVoter; | ||
| address public constant angle = address(0x31429d1856aD1377A8A0079410B297e1a9e214c2); | ||
|
|
||
| // gauge => strategies | ||
| mapping(address => address) public strategies; | ||
| mapping(address => bool) public voters; | ||
| address public governance; | ||
|
|
||
| uint256 lastTimeCursor; | ||
|
charlesndalton marked this conversation as resolved.
Outdated
|
||
|
|
||
| constructor(address _voter) public { | ||
| governance = address(0xFEB4acf3df3cDEA7399794D0869ef76A6EfAff52); | ||
| yearnAngleVoter = YearnAngleVoter(_voter); | ||
| } | ||
|
|
||
| function setGovernance(address _governance) external { | ||
| require(msg.sender == governance, "!governance"); | ||
| governance = _governance; | ||
| } | ||
|
|
||
| function approveStrategy(address _gauge, address _strategy) external { | ||
| require(msg.sender == governance, "!governance"); | ||
| strategies[_gauge] = _strategy; | ||
| } | ||
|
|
||
| function revokeStrategy(address _gauge) external { | ||
| require(msg.sender == governance, "!governance"); | ||
| strategies[_gauge] = address(0); | ||
| } | ||
|
|
||
| function approveVoter(address _voter) external { | ||
| require(msg.sender == governance, "!governance"); | ||
| voters[_voter] = true; | ||
| } | ||
|
charlesndalton marked this conversation as resolved.
|
||
|
|
||
| function revokeVoter(address _voter) external { | ||
| require(msg.sender == governance, "!governance"); | ||
| voters[_voter] = false; | ||
| } | ||
|
|
||
| function lock(uint256 amount, uint256 unlockTime) external { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you see any harm adjusting this function to have zero arguments? My thought is that we always max lock the full balance. There is no access control on this function, what if someone locks for less than max? Do we care?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I included a msg.sender check to it too to check for governance, I normally prefer to leave things as flexible as possible just in case. Wdyt? |
||
| if (amount > 0) { | ||
| IERC20(angle).transfer(address(yearnAngleVoter), amount); | ||
| yearnAngleVoter.createLock(amount, unlockTime); | ||
| } | ||
| } | ||
|
|
||
| function increaseAmount(uint256 amount) external { | ||
| if (amount > 0) { | ||
| IERC20(angle).transfer(address(yearnAngleVoter), amount); | ||
| yearnAngleVoter.increaseAmount(amount); | ||
| } | ||
| } | ||
|
|
||
| function vote(address _gauge, uint256 _amount) public { | ||
| require(voters[msg.sender], "!voter"); | ||
| yearnAngleVoter.safeExecute(_gauge, 0, abi.encodeWithSignature("vote_for_gauge_weights(address,uint256)", _gauge, _amount)); | ||
| } | ||
|
|
||
| function withdraw( | ||
| address _gauge, | ||
| address _token, | ||
| uint256 _amount | ||
| ) public returns (uint256) { | ||
| require(strategies[_gauge] == msg.sender, "!strategy"); | ||
| uint256 _balance = IERC20(_token).balanceOf(address(yearnAngleVoter)); | ||
| yearnAngleVoter.safeExecute(_gauge, 0, abi.encodeWithSignature("withdraw(uint256)", _amount)); | ||
| _balance = IERC20(_token).balanceOf(address(yearnAngleVoter)) - _balance; | ||
| yearnAngleVoter.safeExecute(_token, 0, abi.encodeWithSignature("transfer(address,uint256)", msg.sender, _balance)); | ||
|
charlesndalton marked this conversation as resolved.
|
||
| return _balance; | ||
| } | ||
|
|
||
| function withdrawFromStableMaster(address stableMaster, uint256 amount, | ||
| address poolManager, address token, address gauge) external { | ||
|
16slim marked this conversation as resolved.
|
||
| require(strategies[gauge] == msg.sender, "!strategy"); | ||
|
|
||
| IERC20(token).safeTransfer(address(yearnAngleVoter), amount); | ||
|
|
||
| yearnAngleVoter.safeExecute(token, 0, abi.encodeWithSignature("approve(address,uint256)", stableMaster, 0)); | ||
| yearnAngleVoter.safeExecute(token, 0, abi.encodeWithSignature("approve(address,uint256)", stableMaster, amount)); | ||
|
charlesndalton marked this conversation as resolved.
Outdated
|
||
|
|
||
| yearnAngleVoter.safeExecute(stableMaster, 0, abi.encodeWithSignature( | ||
| "withdraw(uint256,address,address,address)", | ||
| amount, | ||
| address(yearnAngleVoter), | ||
| msg.sender, | ||
| poolManager | ||
| )); | ||
| } | ||
|
|
||
| function balanceOf(address _gauge) public view returns (uint256) { | ||
| return IERC20(_gauge).balanceOf(address(yearnAngleVoter)); | ||
| } | ||
|
|
||
| function withdrawAll(address _gauge, address _token) external returns (uint256) { | ||
|
charlesndalton marked this conversation as resolved.
|
||
| require(strategies[_gauge] == msg.sender, "!strategy"); | ||
| return withdraw(_gauge, _token, balanceOf(_gauge)); | ||
| } | ||
|
|
||
| function deposit(address gauge, uint256 amount, address token) external { | ||
| require(strategies[gauge] == msg.sender, "!strategy"); | ||
|
|
||
| yearnAngleVoter.safeExecute(token, 0, abi.encodeWithSignature("approve(address,uint256)", gauge, 0)); | ||
| yearnAngleVoter.safeExecute(token, 0, abi.encodeWithSignature("approve(address,uint256)", gauge, amount)); | ||
|
16slim marked this conversation as resolved.
Outdated
|
||
|
|
||
| yearnAngleVoter.safeExecute(gauge, 0, abi.encodeWithSignature( | ||
| "deposit(uint256)", | ||
| amount | ||
| )); | ||
| } | ||
|
|
||
| function depositToStableMaster(address stableMaster, uint256 amount, | ||
| address poolManager, address token, address gauge) external { | ||
| require(strategies[gauge] == msg.sender, "!strategy"); | ||
|
|
||
| IERC20(token).safeTransfer(address(yearnAngleVoter), amount); | ||
|
|
||
| yearnAngleVoter.safeExecute(token, 0, abi.encodeWithSignature("approve(address,uint256)", stableMaster, 0)); | ||
| yearnAngleVoter.safeExecute(token, 0, abi.encodeWithSignature("approve(address,uint256)", stableMaster, amount)); | ||
|
charlesndalton marked this conversation as resolved.
Outdated
|
||
| yearnAngleVoter.safeExecute(stableMaster, 0, abi.encodeWithSignature( | ||
| "deposit(uint256,address,address)", | ||
| amount, | ||
| address(yearnAngleVoter), | ||
| poolManager | ||
| )); | ||
| } | ||
|
|
||
| function claimRewards(address _gauge, address _token) external { | ||
| require(strategies[_gauge] == msg.sender, "!strategy"); | ||
| yearnAngleVoter.safeExecute( | ||
| _gauge, | ||
| 0, | ||
| abi.encodeWithSelector( | ||
| IAngleGauge.claim_rewards.selector | ||
| ) | ||
| ); | ||
| yearnAngleVoter.safeExecute(_token, 0, abi.encodeWithSignature("transfer(address,uint256)", msg.sender, IERC20(_token).balanceOf(address(yearnAngleVoter)))); | ||
| } | ||
|
|
||
| function balanceOfSanToken(address sanToken) public view returns (uint256) { | ||
| return IERC20(sanToken).balanceOf(address(yearnAngleVoter)); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -18,6 +18,8 @@ import "./interfaces/Angle/IStableMaster.sol"; | |||||||
| import "./interfaces/Angle/IAngleGauge.sol"; | ||||||||
| import "./interfaces/Yearn/ITradeFactory.sol"; | ||||||||
| import "./interfaces/Uniswap/IUniV2.sol"; | ||||||||
| import {AngleStrategyVoterProxy} from "./AngleStrategyVoterProxy.sol"; | ||||||||
|
|
||||||||
|
|
||||||||
| interface IBaseFee { | ||||||||
| function isCurrentBaseFeeAcceptable() external view returns (bool); | ||||||||
|
|
@@ -33,6 +35,8 @@ contract Strategy is BaseStrategy { | |||||||
|
|
||||||||
| IERC20 public constant angleToken = IERC20(0x31429d1856aD1377A8A0079410B297e1a9e214c2); | ||||||||
| IStableMaster public constant angleStableMaster = IStableMaster(0x5adDc89785D75C86aB939E9e15bfBBb7Fc086A87); | ||||||||
| AngleStrategyVoterProxy public strategyProxy; | ||||||||
|
|
||||||||
| uint256 public constant MAX_BPS = 10000; | ||||||||
|
|
||||||||
| // variable for determining how much governance token to hold for voting rights | ||||||||
|
|
@@ -56,13 +60,15 @@ contract Strategy is BaseStrategy { | |||||||
| address _vault, | ||||||||
| address _sanToken, | ||||||||
| address _sanTokenGauge, | ||||||||
| address _poolManager | ||||||||
| address _poolManager, | ||||||||
| address _strategyProxy | ||||||||
| ) public BaseStrategy(_vault) { | ||||||||
| // Constructor should initialize local variables | ||||||||
| _initializeStrategy( | ||||||||
| _sanToken, | ||||||||
| _sanTokenGauge, | ||||||||
| _poolManager | ||||||||
| _poolManager, | ||||||||
| _strategyProxy | ||||||||
| ); | ||||||||
| } | ||||||||
|
|
||||||||
|
|
@@ -71,11 +77,13 @@ contract Strategy is BaseStrategy { | |||||||
| function _initializeStrategy( | ||||||||
| address _sanToken, | ||||||||
| address _sanTokenGauge, | ||||||||
| address _poolManager | ||||||||
| address _poolManager, | ||||||||
| address _strategyProxy | ||||||||
| ) internal { | ||||||||
| sanToken = IERC20(_sanToken); | ||||||||
| sanTokenGauge = IAngleGauge(_sanTokenGauge); | ||||||||
| poolManager = _poolManager; | ||||||||
| strategyProxy = AngleStrategyVoterProxy(_strategyProxy); | ||||||||
|
|
||||||||
| percentKeep = 1000; | ||||||||
| healthCheck = 0xDDCea799fF1699e98EDF118e0629A974Df7DF012; | ||||||||
|
|
@@ -97,13 +105,15 @@ contract Strategy is BaseStrategy { | |||||||
| address _keeper, | ||||||||
| address _sanToken, | ||||||||
| address _sanTokenGauge, | ||||||||
| address _poolManager | ||||||||
| address _poolManager, | ||||||||
| address _strategyProxy | ||||||||
| ) external { | ||||||||
| _initialize(_vault, _strategist, _rewards, _keeper); | ||||||||
| _initializeStrategy( | ||||||||
| _sanToken, | ||||||||
| _sanTokenGauge, | ||||||||
| _poolManager | ||||||||
| _poolManager, | ||||||||
| _strategyProxy | ||||||||
| ); | ||||||||
| } | ||||||||
|
|
||||||||
|
|
@@ -114,7 +124,8 @@ contract Strategy is BaseStrategy { | |||||||
| address _keeper, | ||||||||
| address _sanToken, | ||||||||
| address _sanTokenGauge, | ||||||||
| address _poolManager | ||||||||
| address _poolManager, | ||||||||
| address _strategyProxy | ||||||||
| ) external returns (address newStrategy) { | ||||||||
| require(isOriginal, "!clone"); | ||||||||
| bytes20 addressBytes = bytes20(address(this)); | ||||||||
|
|
@@ -141,7 +152,8 @@ contract Strategy is BaseStrategy { | |||||||
| _keeper, | ||||||||
| _sanToken, | ||||||||
| _sanTokenGauge, | ||||||||
| _poolManager | ||||||||
| _poolManager, | ||||||||
| _strategyProxy | ||||||||
| ); | ||||||||
|
|
||||||||
| emit Cloned(newStrategy); | ||||||||
|
|
@@ -208,14 +220,14 @@ contract Strategy is BaseStrategy { | |||||||
| } | ||||||||
|
|
||||||||
| // Claim rewards here so that we can chain tend() -> yswap sell -> harvest() in a single transaction | ||||||||
| sanTokenGauge.claim_rewards(); | ||||||||
| strategyProxy.claimRewards(address(sanTokenGauge), address(angleToken)); | ||||||||
|
charlesndalton marked this conversation as resolved.
Outdated
|
||||||||
|
|
||||||||
| uint256 _tokensAvailable = balanceOfAngleToken(); | ||||||||
| if (_tokensAvailable > 0) { | ||||||||
| uint256 _tokensToGov = | ||||||||
| uint256 _tokensToKeep = | ||||||||
| (_tokensAvailable * percentKeep) / MAX_BPS; | ||||||||
| if (_tokensToGov > 0) { | ||||||||
| angleToken.transfer(treasury, _tokensToGov); | ||||||||
| if (_tokensToKeep > 0) { | ||||||||
| IERC20(angleToken).transfer(address(strategyProxy), _tokensToKeep); | ||||||||
|
16slim marked this conversation as resolved.
Outdated
|
||||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
|
|
@@ -230,13 +242,14 @@ contract Strategy is BaseStrategy { | |||||||
| uint256 _wantAvailable = _balanceOfWant - _debtOutstanding; | ||||||||
| if (_wantAvailable > 0) { | ||||||||
| // deposit for sanToken | ||||||||
| want.safeTransfer(address(strategyProxy), _wantAvailable); | ||||||||
| depositToStableMaster(_wantAvailable); | ||||||||
| } | ||||||||
|
|
||||||||
| // Stake any san tokens, whether they originated through the above deposit or some other means (e.g. migration) | ||||||||
| uint256 _sanTokenBalance = balanceOfSanToken(); | ||||||||
| if (_sanTokenBalance > 0) { | ||||||||
| sanTokenGauge.deposit(_sanTokenBalance); | ||||||||
| strategyProxy.deposit(address(sanTokenGauge), _sanTokenBalance, address(sanToken)); | ||||||||
|
charlesndalton marked this conversation as resolved.
Outdated
|
||||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
|
|
@@ -278,12 +291,16 @@ contract Strategy is BaseStrategy { | |||||||
|
|
||||||||
| uint256 _sanTokenBalance = balanceOfSanToken(); | ||||||||
| if (_amountInSanToken > _sanTokenBalance) { | ||||||||
| sanTokenGauge.withdraw( | ||||||||
| Math.min(_amountInSanToken - _sanTokenBalance, balanceOfStakedSanToken()) | ||||||||
| _amountInSanToken = Math.min(_amountInSanToken - _sanTokenBalance, balanceOfStakedSanToken()); | ||||||||
| strategyProxy.withdraw( | ||||||||
| address(sanTokenGauge), | ||||||||
| address(sanToken), | ||||||||
| _amountInSanToken | ||||||||
| ); | ||||||||
| IERC20(sanToken).safeTransfer(address(strategyProxy), _amountInSanToken); | ||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This makes sense when you think about it, and it's also kind of unintuitive. Would we still need to do this if stablemaster operations were done in the strategy (e.g., want -> sanToken and back)? Is that even possible?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess it's more of a design choice, I wanted the strategy.sol contract to not do anything and let the proxy handle it but happy to change my mind if you think it's optimal
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I guess there's not a clear better way. My personal thinking is that it makes sense for the voter and proxy to be responsible for gauge staking and voting, and let the strategy be responsible for sanToken <-> want stuff, but I'm okay if you think it makes more sense this way |
||||||||
| } | ||||||||
|
|
||||||||
| withdrawFromStableMaster(Math.min(_amountInSanToken, balanceOfSanToken())); | ||||||||
| withdrawFromStableMaster(Math.min(_amountInSanToken, _amountInSanToken)); | ||||||||
|
charlesndalton marked this conversation as resolved.
Outdated
|
||||||||
| } | ||||||||
|
|
||||||||
| // can be used in conjunction with migration if this function is still working | ||||||||
|
|
@@ -294,9 +311,21 @@ contract Strategy is BaseStrategy { | |||||||
| // transfers all tokens to new strategy | ||||||||
| function prepareMigration(address _newStrategy) internal override { | ||||||||
| // want is transferred by the base contract's migrate function | ||||||||
| sanTokenGauge.withdraw(balanceOfStakedSanToken()); | ||||||||
| // sanTokenGauge.withdraw(balanceOfStakedSanToken()); | ||||||||
|
charlesndalton marked this conversation as resolved.
Outdated
|
||||||||
|
|
||||||||
| uint256 _angleBalance = balanceOfAngleToken(); | ||||||||
| if(_angleBalance > 0) { | ||||||||
|
charlesndalton marked this conversation as resolved.
Outdated
|
||||||||
| IERC20(angleToken).safeTransfer(_newStrategy, _angleBalance); | ||||||||
| } | ||||||||
|
|
||||||||
| uint256 _stakedBalance = balanceOfStakedSanToken(); | ||||||||
| if (_stakedBalance > 0) { | ||||||||
| strategyProxy.withdraw(address(sanTokenGauge), address(sanToken), _stakedBalance); | ||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why not
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I forgot about its existence, should be same result but replacing as it's exactly its purpose! |
||||||||
| IERC20(sanToken).safeTransfer(address(strategyProxy), _stakedBalance); | ||||||||
| withdrawFromStableMaster(_stakedBalance); | ||||||||
| } | ||||||||
|
|
||||||||
| IERC20(sanToken).safeTransfer(_newStrategy, balanceOfSanToken()); | ||||||||
| IERC20(sanToken).safeTransfer(_newStrategy, IERC20(sanToken).balanceOf(address(this))); | ||||||||
| IERC20(angleToken).transfer(_newStrategy, balanceOfAngleToken()); | ||||||||
| } | ||||||||
|
|
||||||||
|
|
@@ -424,19 +453,18 @@ contract Strategy is BaseStrategy { | |||||||
| percentKeep = _percentKeep; | ||||||||
| } | ||||||||
|
|
||||||||
|
|
||||||||
| // ----------------- SUPPORT & UTILITY FUNCTIONS ---------- | ||||||||
|
|
||||||||
| function balanceOfWant() public view returns (uint256) { | ||||||||
| return want.balanceOf(address(this)); | ||||||||
| } | ||||||||
|
|
||||||||
| function balanceOfStakedSanToken() public view returns (uint256) { | ||||||||
| return IERC20(address(sanTokenGauge)).balanceOf(address(this)); | ||||||||
| return strategyProxy.balanceOf(address(sanTokenGauge)); | ||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we could add a helper function like you did with balanceOfSanToken?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry I am not understanding, it's the same case as
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was saying add a balanceOfStakedSanToken function in the proxy, like this: angle_protocol/src/AngleStrategyVoterProxy.sol Lines 167 to 169 in d7571d9
|
||||||||
| } | ||||||||
|
|
||||||||
| function balanceOfSanToken() public view returns (uint256) { | ||||||||
| return sanToken.balanceOf(address(this)); | ||||||||
| return strategyProxy.balanceOfSanToken(address(sanToken)); | ||||||||
| } | ||||||||
|
|
||||||||
| function balanceOfAngleToken() public view returns (uint256) { | ||||||||
|
|
@@ -472,19 +500,22 @@ contract Strategy is BaseStrategy { | |||||||
| } | ||||||||
|
|
||||||||
| function depositToStableMaster(uint256 _amount) internal { | ||||||||
| IStableMaster(angleStableMaster).deposit( | ||||||||
| strategyProxy.depositToStableMaster( | ||||||||
| address(angleStableMaster), | ||||||||
| _amount, | ||||||||
| address(this), | ||||||||
| poolManager | ||||||||
| poolManager, | ||||||||
| address(want), | ||||||||
| address(sanTokenGauge) | ||||||||
| ); | ||||||||
| } | ||||||||
|
|
||||||||
| function withdrawFromStableMaster(uint256 _amountInSanToken) internal { | ||||||||
| IStableMaster(angleStableMaster).withdraw( | ||||||||
| strategyProxy.withdrawFromStableMaster( | ||||||||
| address(angleStableMaster), | ||||||||
| _amountInSanToken, | ||||||||
| address(this), | ||||||||
| address(this), | ||||||||
| poolManager | ||||||||
| poolManager, | ||||||||
| address(sanToken), | ||||||||
| address(sanTokenGauge) | ||||||||
| ); | ||||||||
| } | ||||||||
|
|
||||||||
|
|
||||||||
Uh oh!
There was an error while loading. Please reload this page.