-
Notifications
You must be signed in to change notification settings - Fork 889
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
Onsen Allocator update #278
base: allocators
Are you sure you want to change the base?
Changes from all commits
ecc2c59
8e5449e
e989a89
a416445
8c55efd
666a184
0d1cc7e
d0cd7c6
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,291 @@ | ||
// SPDX-License-Identifier: AGPL-3.0-or-later | ||
pragma solidity ^0.8.10; | ||
import "../libraries/Address.sol"; | ||
|
||
/// ========== TYPES ========== | ||
import "../types/BaseAllocator.sol"; | ||
|
||
/// ========== INTERFACES ========== | ||
import "./interfaces/IMasterChef.sol"; | ||
import "./interfaces/ISushiBar.sol"; | ||
|
||
struct PoolData { | ||
bool poolActive; | ||
uint256 id; | ||
} | ||
|
||
/** | ||
* @notice Contract deploys liquidity from treasury into the Onsen program, | ||
* earning $SUSHI that can be staked and/or deposited into the treasury. | ||
* Set the Onsen Pool Id for the LP tokens that will be processed ahead of time by calling setLPTokenOnsenPoolId | ||
*/ | ||
contract OnsenAllocatorV2 is BaseAllocator { | ||
using SafeERC20 for IERC20; | ||
|
||
/// ========== STATE VARIABLES ========== | ||
|
||
address public sushi; /// $SUSHI token | ||
address public xSushi; /// $xSUSHI token | ||
address public masterChef; /// Onsen contract | ||
|
||
address public treasury; /// Olympus Treasury | ||
|
||
mapping(address => PoolData) internal _lpToOnsenId; | ||
|
||
/// ========== CONSTRUCTOR ========== | ||
|
||
constructor( | ||
address _chef, | ||
address _sushi, | ||
address _xSushi, | ||
address _treasury, | ||
AllocatorInitData memory data | ||
) BaseAllocator(data) { | ||
require(_chef != address(0)); | ||
masterChef = _chef; | ||
require(_sushi != address(0)); | ||
sushi = _sushi; | ||
require(_xSushi != address(0)); | ||
xSushi = _xSushi; | ||
require(_treasury != address(0)); | ||
treasury = _treasury; | ||
|
||
/** @dev approve for safety, sushi is being staked and xsushi is being instantly sent to treasury and that is fine | ||
* but to be absolutely safe this one approve won't hurt | ||
*/ | ||
IERC20(sushi).approve(address(extender), type(uint256).max); | ||
IERC20(xSushi).approve(address(extender), type(uint256).max); | ||
} | ||
|
||
/** | ||
* @notice Find out which LP tokens to be deposited into onsen, then deposit it into onsen pool and stake the sushi tokens that are returned. | ||
* 1- Based on the LP token to be deposited, find out the Onsen Pool Id | ||
* 2- Deposit LP token into onsen, if deposit is succesfull we should get in return sushi tokens | ||
* 2- Stake the Sushi rewards tokens returned from deposit LP tokens and as rewards form onsen LP stake, we should get in return xSushi tokens | ||
* 3- Keep the xSushi on the allocator contract for easily deallocation | ||
* 4- Calculate gains/loss based on the LP balance on the Onsen Pool. | ||
*/ | ||
function _update(uint256 id) internal override returns (uint128 gain, uint128 loss) { | ||
uint256 index = tokenIds[id]; | ||
IERC20 LPtoken = _tokens[index]; | ||
uint256 balance = LPtoken.balanceOf(address(this)); | ||
|
||
/// Find out if there is a Onsen Pool for the LPToken | ||
(bool onsenLPFound, uint256 onsenPoolId) = _findPoolByLP(address(LPtoken)); | ||
|
||
/// Deposit LP token into onsen, if deposit succesfull this address should have in return sushi tokens | ||
if (balance > 0) { | ||
calusaca marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (onsenLPFound) { | ||
/// Approve and deposit balance into onsen Pool | ||
LPtoken.approve(masterChef, balance); | ||
IMasterChef(masterChef).deposit(onsenPoolId, balance, address(this)); | ||
} else { | ||
/// If no Onsen pool was found for the LP token then return LP token to the treasury. | ||
LPtoken.safeTransfer(treasury, balance); | ||
} | ||
} else { | ||
/// If LP token balance is 0, then harvest any pending Sushi rewards for it to be stake. | ||
IMasterChef(masterChef).harvest(onsenPoolId, address(this)); | ||
} | ||
|
||
/// Stake the sushi tokens | ||
_stakeSushi(); | ||
|
||
///Calculate gains/loss | ||
/// Retrieve current balance for pool and address | ||
UserInfo memory currentUserInfo = IMasterChef(masterChef).userInfo(onsenPoolId, address(this)); | ||
|
||
uint256 last = extender.getAllocatorAllocated(id) + extender.getAllocatorPerformance(id).gain; | ||
|
||
if (currentUserInfo.amount >= last) { | ||
gain = uint128(currentUserInfo.amount - last); | ||
} else { | ||
loss = uint128(last - currentUserInfo.amount); | ||
} | ||
} | ||
|
||
function deallocate(uint256[] memory amounts) public override onlyGuardian { | ||
for (uint256 i; i < amounts.length; i++) { | ||
uint256 amount = amounts[i]; | ||
if (amount > 0) { | ||
/// Get the LPToken | ||
IERC20 token = _tokens[i]; | ||
|
||
/// Get the onsen pool Id | ||
(bool onsenLPFound, uint256 onsenPoolId) = _findPoolByLP(address(token)); | ||
|
||
if (onsenLPFound) { | ||
/// Withdraw LP Token from Onsen Pool | ||
IMasterChef(masterChef).withdrawAndHarvest(onsenPoolId, amount, address(this)); | ||
} | ||
} | ||
} | ||
} | ||
|
||
function _deactivate(bool panic) internal override { | ||
/// If Panic transfer LP tokens to the treasury | ||
if (panic) { | ||
/// Get amounts by LP to unstake and then deallocate (unstake) each token. | ||
_deallocateAll(); | ||
_unstakeSushi(); | ||
|
||
for (uint256 i; i < _tokens.length; i++) { | ||
IERC20 token = _tokens[i]; | ||
token.safeTransfer(treasury, token.balanceOf(address(this))); | ||
} | ||
|
||
/// And transfer Sushi tokens (rewards) to treasury | ||
IERC20(sushi).safeTransfer(treasury, IERC20(sushi).balanceOf(address(this))); | ||
} | ||
} | ||
|
||
function _prepareMigration() internal override { | ||
/// Get amounts by LP to unstake and then deallocate (unstake) each token. | ||
_deallocateAll(); | ||
_unstakeSushi(); | ||
} | ||
|
||
function amountAllocated(uint256 id) public view override returns (uint256) { | ||
/// Find the LP token which amount allocated is requested | ||
uint256 index = tokenIds[id]; | ||
IERC20 LPtoken = _tokens[index]; | ||
PoolData memory pd = _lpToOnsenId[address(LPtoken)]; | ||
|
||
if (pd.poolActive) { | ||
UserInfo memory currentUserInfo = IMasterChef(masterChef).userInfo(pd.id, address(this)); | ||
return currentUserInfo.amount; | ||
} else { | ||
return 0; | ||
} | ||
} | ||
|
||
function rewardTokens() public view override returns (IERC20[] memory) { | ||
IERC20[] memory rewards = new IERC20[](2); | ||
rewards[0] = IERC20(sushi); | ||
rewards[1] = IERC20(xSushi); | ||
return rewards; | ||
} | ||
|
||
function utilityTokens() public view override returns (IERC20[] memory) { | ||
IERC20[] memory utility = new IERC20[](1); | ||
utility[0] = IERC20(sushi); | ||
return utility; | ||
} | ||
|
||
function name() external pure override returns (string memory) { | ||
return "OnsenAllocator"; | ||
} | ||
|
||
function setTreasury(address treasuryAddress) external onlyGuardian { | ||
treasury = treasuryAddress; | ||
} | ||
|
||
function setSushi(address sushiAddress) external onlyGuardian { | ||
sushi = sushiAddress; | ||
} | ||
|
||
function setXSushi(address xSushiAddress) external onlyGuardian { | ||
xSushi = xSushiAddress; | ||
} | ||
|
||
function setMasterChefAddress(address masterChefAddress) external onlyGuardian { | ||
masterChef = masterChefAddress; | ||
} | ||
|
||
/** | ||
* @notice Set Onsen Pool Id for LP token | ||
* @param lpToken address of the LP token | ||
* @param onsenPoolId Pool id from onsen sushi | ||
*/ | ||
function setLPTokenOnsenPoolId(address lpToken, uint256 onsenPoolId) external onlyGuardian { | ||
PoolData memory pd = _lpToOnsenId[lpToken]; | ||
pd.poolActive = true; | ||
pd.id = onsenPoolId; | ||
} | ||
|
||
/** | ||
* @notice Get the stored Onsen Pool id based on the LP token address | ||
* @param lpToken address of the LP token | ||
* @return first value will show if the value is stored , second value show the Onsen Pool Id stored locally. | ||
*/ | ||
function getLPTokenOnsenPoolId(address lpToken) external view returns (bool, uint256) { | ||
/// Check if the id is already stored in the local variable | ||
PoolData memory pd = _lpToOnsenId[lpToken]; | ||
|
||
/// If the pool is active return the id else search for the pool id | ||
if (pd.poolActive) { | ||
return (pd.poolActive, pd.id); | ||
} else { | ||
return (false, 0); | ||
} | ||
} | ||
|
||
/// ========== INTERNAL FUNCTIONS ========== | ||
|
||
/** | ||
* @notice stake sushi rewards | ||
*/ | ||
function _stakeSushi() internal { | ||
uint256 balance = IERC20(sushi).balanceOf(address(this)); | ||
if (balance > 0) { | ||
IERC20(sushi).approve(xSushi, balance); | ||
ISushiBar(xSushi).enter(balance); // stake sushi | ||
} | ||
} | ||
|
||
/** | ||
* @notice unStake sushi rewards | ||
*/ | ||
function _unstakeSushi() internal { | ||
uint256 balance = IERC20(xSushi).balanceOf(address(this)); | ||
if (balance > 0) { | ||
ISushiBar(xSushi).leave(balance); // unstake $xSUSHI | ||
} | ||
} | ||
|
||
/** | ||
* @notice Find out pool Id from Onsen based on the LP token | ||
* according to Onsen documentation there shouldn't be more than pool by LPToken, so we are going | ||
* to use the first ocurrence of the LPToken in the Onsen pools | ||
* @return [bool, uint256], first value shows if value was found, second value the id for the pool | ||
*/ | ||
function _findPoolByLP(address LPToken) internal returns (bool, uint256) { | ||
/// Check if the id is already stored in the local variable | ||
PoolData memory pd = _lpToOnsenId[LPToken]; | ||
|
||
/// If the pool is active return the id else search for the pool id | ||
if (pd.poolActive) { | ||
return (pd.poolActive, pd.id); | ||
} else { | ||
/// Call the poolLength function from sushi masterchef v2 and compare against the LP token passed as parameter | ||
uint256 poolsLength = IMasterChef(masterChef).poolLength(); | ||
|
||
for (uint256 i; i < poolsLength; i++) { | ||
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. Poollength of masterchef is 355 i think unless im looking at wrong contract. i think this can be done offchain. We can figure out the id mappings offchain using view functions on masterchef and insert them in with a setter function. This would work but im worried it will use alot more gas than we need to. 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. Added the following 2 functions: function setLPTokenOnsenPoolId(address lpToken, uint256 onsenPoolId) external onlyGuardian So we can add the Onsen Pool Id for the LP Tokens ahead of time and also check if the LP token was already added, worst-case scenario would be that the token is not added and it will go search for it based on the Onsen Pool Length. @Paul4912 Please let me know if this is fine or if we need to be able to add all Onsen Pool Ids at once? 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. Yea that's how id imagine we'd do it. Idk how many onsen farms there are to need a batch add. 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. @PARABRAHMAN0 @ind-igo Do you have any idea about how many Onsen Pools are used? Or do you know where I can check this? |
||
/// If found, return the the pool Id from Onsen for the current LPToken. | ||
if (LPToken == (address)(IMasterChef(masterChef).lpToken(i))) { | ||
pd = _lpToOnsenId[LPToken]; | ||
pd.poolActive = true; | ||
calusaca marked this conversation as resolved.
Show resolved
Hide resolved
|
||
pd.id = i; | ||
return (true, i); | ||
} | ||
} | ||
|
||
/// If the LPToken was not found return 0 and indicate that the token was not found | ||
return (false, 0); | ||
} | ||
} | ||
|
||
function _deallocateAll() internal { | ||
/// Find the Onsen Pools Id for each token and withdraw and harvest each pool. | ||
for (uint256 i; i < _tokens.length; i++) { | ||
(bool onsenLPFound, uint256 onsenPoolId) = _findPoolByLP(address(_tokens[i])); | ||
if (onsenLPFound) { | ||
/// Retrieve current balance for pool and address | ||
UserInfo memory currentUserInfo = IMasterChef(masterChef).userInfo(onsenPoolId, address(this)); | ||
if (currentUserInfo.amount > 0) { | ||
IMasterChef(masterChef).withdrawAndHarvest(onsenPoolId, currentUserInfo.amount, address(this)); | ||
} | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
// SPDX-License-Identifier: AGPL-3.0 | ||
pragma solidity >=0.7.5; | ||
|
||
struct UserInfo { | ||
uint256 amount; // How many LP tokens the user has provided. | ||
uint256 rewardDebt; | ||
} | ||
|
||
interface IMasterChef { | ||
function pendingSushi(uint256 _pid, address _user) external view returns (uint256); | ||
|
||
function deposit( | ||
uint256 _pid, | ||
uint256 _amount, | ||
address _to | ||
) external; | ||
|
||
function withdraw( | ||
uint256 _pid, | ||
uint256 _amount, | ||
address _to | ||
) external; | ||
|
||
function withdrawAndHarvest( | ||
uint256 _pid, | ||
uint256 _amount, | ||
address _to | ||
) external; | ||
|
||
function emergencyWithdraw(uint256 _pid) external; | ||
|
||
function userInfo(uint256 _pid, address _user) external view returns (UserInfo memory); | ||
|
||
function poolLength() external view returns (uint256); | ||
|
||
function lpToken(uint256) external view returns (address); | ||
|
||
function harvest(uint256 pid, address to) external; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
// SPDX-License-Identifier: AGPL-3.0 | ||
pragma solidity >=0.7.5; | ||
|
||
interface ISushiBar { | ||
function enter(uint256 _amount) external; | ||
|
||
function leave(uint256 _share) external; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if we want to add more tokens? Add a setter for more LP tokens, should be easy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@PARABRAHMAN0 Sorry but not sure about what this means, or maybe is already added based on a previous comment with the following 2 functions?
function setLPTokenOnsenPoolId(address lpToken, uint256 onsenPoolId) external onlyGuardian
function getLPTokenOnsenPoolId(address lpToken) external view returns (bool, uint256)