-
Notifications
You must be signed in to change notification settings - Fork 39
Staking Vault #291
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
Open
Drypto13
wants to merge
26
commits into
permissionless-listings
Choose a base branch
from
pl-staking-vault
base: permissionless-listings
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
Staking Vault #291
Changes from all commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
f939a01
staking vault init commit
Drypto13 f84c1dc
Merge remote-tracking branch 'origin/permissionless-listings' into pl…
Drypto13 95bf274
fix issues
Drypto13 eed501a
add test case
Drypto13 0864a22
full test case for PL staking vault
Drypto13 841fb69
added events and minor optimization
Drypto13 d5cb5a9
Create IStakingVault.sol
Drypto13 2b7d366
Merge branch 'permissionless-listings' into pl-staking-vault
Drypto13 0c80b7b
Update LoanTokenLogicStandard.sol
Drypto13 943bde3
Update HelperImpl.sol
Drypto13 8e273a5
Update DAppHelper.sol
Drypto13 0c83b2d
Update IToken.sol
Drypto13 90412f7
Merge branch 'permissionless-listings' into pl-staking-vault
Drypto13 586856d
Update staking-vault.py
Drypto13 d3b1bf6
Minor changes
Drypto13 e2cca1b
Merge branch 'permissionless-listings' into pl-staking-vault
0xosama e02b411
format
0xosama 78d6f29
restricted to view
0xosama 1f24733
Merge branch 'permissionless-listings' into pl-staking-vault
0xosama 2d823e7
changes
Drypto13 028c1c6
constants uppercase
0xosama 22e5208
made constants public
0xosama da568a1
remove func from interface
0xosama ba0284b
renamed variable to make it consistent with other namings
0xosama ef85ed3
Merge branch 'permissionless-listings' into pl-staking-vault
0xosama 37bffdf
load bool to memory
Drypto13 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 |
---|---|---|
|
@@ -81,10 +81,11 @@ dependencies: | |
- openzeppelin/[email protected] | ||
- openzeppelin/[email protected] | ||
- openzeppelin/[email protected] | ||
- openzeppelin/[email protected].0 | ||
- openzeppelin/[email protected].0 | ||
- openzeppelin/[email protected].2 | ||
- openzeppelin/[email protected].1 | ||
- paulrberg/[email protected] | ||
- uniswap/[email protected] | ||
- uniswap/[email protected] | ||
- uniswap/[email protected] | ||
- uniswap/[email protected] | ||
- uniswap/[email protected] | ||
- uniswap/[email protected] | ||
- celer-network/[email protected] | ||
dev_deployment_artifacts: false |
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,267 @@ | ||
pragma solidity ^0.8.0; | ||
|
||
import "@openzeppelin-4.8.0/token/ERC1155/ERC1155.sol"; | ||
import "@openzeppelin-4.8.0/token/ERC20/utils/SafeERC20.sol"; | ||
import "@openzeppelin-4.8.0/token/ERC20/extensions/IERC20Metadata.sol"; | ||
|
||
import "../../interfaces/IPriceFeeds.sol"; | ||
import "../proxies/0_8/Upgradeable_0_8.sol"; | ||
import "../../interfaces/IStakingVault.sol"; | ||
|
||
contract StakingVault is IStakingVault, Upgradeable_0_8, ERC1155 { | ||
using SafeERC20 for IERC20; | ||
struct TokenComposition { | ||
address depositToken; | ||
address tokenToBack; | ||
} | ||
address public immutable PROTOCOL; | ||
IPriceFeeds public immutable PRICE_FEED; | ||
address public immutable VALUATION_TOKEN; | ||
address public immutable REWARD_TOKEN; | ||
|
||
|
||
address[] private _stakingTokens; | ||
mapping(address => bool) public tokenSupported; | ||
mapping(uint256 => uint256) private _sTokenPrice; | ||
mapping(uint256 => TokenComposition) public identifierToTokens; | ||
mapping(uint256 => uint256) public balanceStakedPerID; | ||
0xosama marked this conversation as resolved.
Show resolved
Hide resolved
|
||
mapping(uint256 => uint256) public supplyPerID; | ||
|
||
mapping(address => uint256) public undistributedRewards; | ||
mapping(uint256 => uint256) public rewardsPerID; | ||
|
||
mapping(address => mapping(uint256 => uint256)) public lastClaimRewardAccrual; | ||
mapping(uint256 => bool) public initialized; | ||
mapping(address => uint8) public tokenDecimalCache; | ||
|
||
modifier onlyProtocol() { | ||
0xosama marked this conversation as resolved.
Show resolved
Hide resolved
|
||
require(msg.sender == PROTOCOL, "not protocol"); | ||
_; | ||
} | ||
|
||
constructor(string memory uri_, IPriceFeeds pFeed, address rToken, address p, address vToken) ERC1155(uri_) { | ||
PRICE_FEED = pFeed; | ||
REWARD_TOKEN = rToken; | ||
PROTOCOL = p; | ||
VALUATION_TOKEN = vToken; | ||
} | ||
|
||
function stakingTokens() external view returns (address[] memory) { | ||
return _stakingTokens; | ||
} | ||
|
||
function updateTokenSupport( | ||
address[] memory tokens, | ||
bool[] memory support, | ||
address[] memory listOfSupportedTokens, | ||
uint8[] memory tokenDecimals | ||
) external onlyOwner { | ||
_stakingTokens = listOfSupportedTokens; | ||
require(tokens.length == tokenDecimals.length, "mismatch"); | ||
for (uint256 i; i < tokens.length; ) { | ||
tokenSupported[tokens[i]] = support[i]; | ||
if (support[i]) { | ||
tokenDecimalCache[tokens[i]] = tokenDecimals[i]; | ||
} | ||
unchecked { | ||
++i; | ||
} | ||
} | ||
|
||
emit TokensSupported(listOfSupportedTokens); | ||
} | ||
|
||
function deposit( | ||
address depositToken, | ||
address tokenToBack, | ||
uint256 amount | ||
) external { | ||
require(tokenSupported[depositToken], "unsupported deposit token"); | ||
uint256 tokenID = convertToID(depositToken, tokenToBack); | ||
if (identifierToTokens[tokenID].depositToken == address(0)) { | ||
0xosama marked this conversation as resolved.
Show resolved
Hide resolved
|
||
_sTokenPrice[tokenID] = 1e18; | ||
} | ||
identifierToTokens[tokenID] = TokenComposition({depositToken: depositToken, tokenToBack: tokenToBack}); | ||
IERC20(depositToken).safeTransferFrom(msg.sender, address(this), amount); | ||
uint256 mintAmount = _amountToMint(tokenID, amount); | ||
_claimReward(tokenID); | ||
_mint(msg.sender, tokenID, mintAmount, ""); | ||
balanceStakedPerID[tokenID] += amount; | ||
supplyPerID[tokenID] += mintAmount; | ||
|
||
emit Deposit(msg.sender, tokenToBack, depositToken, amount); | ||
} | ||
|
||
function withdraw( | ||
address depositToken, | ||
address tokenToBack, | ||
uint256 amount | ||
) external { | ||
uint256 tokenID = convertToID(depositToken, tokenToBack); | ||
_claimReward(tokenID); | ||
_burn(msg.sender, tokenID, amount); | ||
supplyPerID[tokenID] -= amount; | ||
uint256 sendAmount = _amountToSend(tokenID, amount); | ||
balanceStakedPerID[tokenID] -= sendAmount; | ||
IERC20(depositToken).safeTransfer(msg.sender, sendAmount); | ||
|
||
emit Withdraw(msg.sender, tokenToBack, depositToken, amount); | ||
} | ||
|
||
function drawOnPool( | ||
address tokenBacked, | ||
address tokenToCover, | ||
uint256 amountToCover | ||
) external onlyProtocol returns (uint256[] memory) { | ||
address[] memory tokensStaked = _stakingTokens; | ||
uint256[] memory valuesPerToken = new uint256[](tokensStaked.length); | ||
uint256 totalValue; | ||
for (uint256 i = 0; i < tokensStaked.length; ) { | ||
valuesPerToken[i] = PRICE_FEED.queryReturn(tokensStaked[i], VALUATION_TOKEN, balanceStakedPerID[convertToID(tokensStaked[i], tokenBacked)]); | ||
totalValue += valuesPerToken[i]; | ||
unchecked { | ||
++i; | ||
} | ||
} | ||
uint256[] memory amountDrawnPerToken = new uint256[](tokensStaked.length); | ||
uint256 valueOfCoverage = PRICE_FEED.queryReturn(tokenToCover, VALUATION_TOKEN, amountToCover); | ||
for (uint256 i = 0; i < tokensStaked.length; ) { | ||
amountDrawnPerToken[i] = PRICE_FEED.queryReturn(VALUATION_TOKEN, tokensStaked[i], (valueOfCoverage * valuesPerToken[i]) / totalValue); | ||
IERC20(tokensStaked[i]).safeTransfer(msg.sender, amountDrawnPerToken[i]); | ||
unchecked { | ||
++i; | ||
} | ||
} | ||
|
||
_updatePrice(tokenBacked, amountDrawnPerToken); | ||
|
||
emit PoolDraw(tokenBacked, tokenToCover, amountToCover); | ||
return amountDrawnPerToken; | ||
} | ||
|
||
function getStoredTokenPrice(uint256 ID) external view returns (uint256) { | ||
return _sTokenPrice[ID]; | ||
} | ||
|
||
function getBackingAmount(address token) external view returns (uint256) { | ||
address[] memory tokensStaked = _stakingTokens; | ||
uint256 value; | ||
for (uint256 i; i < tokensStaked.length; ) { | ||
value += PRICE_FEED.queryReturn(tokensStaked[i], VALUATION_TOKEN, balanceStakedPerID[convertToID(token, tokensStaked[i])]); | ||
} | ||
return value; | ||
} | ||
|
||
function getBackingAmountInNativeTokens(address token) external view returns (uint256[] memory balances) { | ||
address[] memory tokensStaked = _stakingTokens; | ||
balances = new uint256[](tokensStaked.length); | ||
for (uint256 i; i < tokensStaked.length; ) { | ||
balances[i] = balanceStakedPerID[convertToID(token, tokensStaked[i])]; | ||
unchecked { | ||
++i; | ||
} | ||
} | ||
} | ||
|
||
function convertToID(address depositToken, address tokenToBack) public pure returns (uint256) { | ||
return uint256(keccak256(abi.encode(depositToken, tokenToBack))); | ||
} | ||
|
||
function addRewards(address tokenBacked, uint256 rewardAmount) external { | ||
_addRewards(rewardAmount, tokenBacked); | ||
IERC20(REWARD_TOKEN).safeTransferFrom(msg.sender, address(this), rewardAmount); | ||
|
||
emit AddRewards(tokenBacked, rewardAmount); | ||
} | ||
|
||
function accumulateRewards(address tokenBacked, uint256 rewardAmount) external { | ||
undistributedRewards[tokenBacked] += rewardAmount; | ||
IERC20(REWARD_TOKEN).safeTransferFrom(msg.sender, address(this), rewardAmount); | ||
|
||
emit AccumulateRewards(tokenBacked, rewardAmount); | ||
} | ||
|
||
function distributeRewards(address tokenBacked) external { | ||
uint256 rewards = undistributedRewards[tokenBacked]; | ||
_addRewards(rewards, tokenBacked); | ||
undistributedRewards[tokenBacked] = 0; | ||
|
||
emit DistributeRewards(tokenBacked, rewards); | ||
} | ||
|
||
function claimRewards(uint256[] memory tokenIDs) external { | ||
for (uint256 i; i < tokenIDs.length; ) { | ||
_claimReward(tokenIDs[i]); | ||
unchecked { | ||
++i; | ||
} | ||
} | ||
} | ||
|
||
function _updatePrice(address tokenBacked, uint256[] memory amountsDrawn) internal { | ||
address[] memory tokensStaked = _stakingTokens; | ||
uint256 previousBalance; | ||
for (uint256 i = 0; i < tokensStaked.length; ) { | ||
uint256 tokenID = convertToID(tokensStaked[i], tokenBacked); | ||
previousBalance = balanceStakedPerID[tokenID]; | ||
balanceStakedPerID[tokenID] = previousBalance - amountsDrawn[i]; | ||
_sTokenPrice[tokenID] = (_sTokenPrice[tokenID] * (previousBalance - balanceStakedPerID[tokenID])) / previousBalance; | ||
unchecked { | ||
++i; | ||
} | ||
} | ||
} | ||
|
||
function _amountToMint(uint256 tokenID, uint256 amount) internal view returns (uint256) { | ||
uint8 decimals = tokenDecimalCache[identifierToTokens[tokenID].depositToken]; | ||
|
||
return ((amount * 1e18) / _sTokenPrice[tokenID]) * 10**(18 - decimals); | ||
} | ||
|
||
function _amountToSend(uint256 tokenID, uint256 amountBurnt) internal view returns (uint256) { | ||
uint8 decimals = IERC20Metadata(identifierToTokens[tokenID].depositToken).decimals(); | ||
|
||
return ((_sTokenPrice[tokenID] * amountBurnt) / 1e18) / 10**(18 - decimals); | ||
} | ||
|
||
function _addRewards(uint256 rewardAmount, address tokenBacked) internal { | ||
address[] memory tokensStaked = _stakingTokens; | ||
uint256 totalValue; | ||
uint256[] memory values = new uint256[](tokensStaked.length); | ||
uint256 tokenID; | ||
for (uint256 i; i < tokensStaked.length; ) { | ||
0xosama marked this conversation as resolved.
Show resolved
Hide resolved
|
||
tokenID = convertToID(tokensStaked[i], tokenBacked); | ||
values[i] = PRICE_FEED.queryReturn(tokensStaked[i], VALUATION_TOKEN, balanceStakedPerID[tokenID]); | ||
totalValue += values[i]; | ||
unchecked { | ||
++i; | ||
} | ||
} | ||
for (uint256 i; i < tokensStaked.length; ) { | ||
tokenID = convertToID(tokensStaked[i], tokenBacked); | ||
values[i] = (values[i] * 1e18) / totalValue; | ||
rewardsPerID[tokenID] += (rewardAmount * values[i]) / supplyPerID[tokenID]; | ||
0xosama marked this conversation as resolved.
Show resolved
Hide resolved
0xosama marked this conversation as resolved.
Show resolved
Hide resolved
|
||
unchecked { | ||
++i; | ||
} | ||
} | ||
} | ||
|
||
function _claimReward(uint256 tokenID) internal { | ||
uint256 previousAmount = lastClaimRewardAccrual[msg.sender][tokenID]; | ||
bool tokenIDInitialized = initialized[tokenID]; | ||
if (previousAmount == 0 && tokenIDInitialized) { | ||
lastClaimRewardAccrual[msg.sender][tokenID] = rewardsPerID[tokenID]; | ||
return; | ||
} | ||
uint256 newAmount = rewardsPerID[tokenID]; | ||
if (newAmount - previousAmount == 0) { | ||
return; | ||
} | ||
IERC20(REWARD_TOKEN).safeTransfer(msg.sender, ((newAmount - previousAmount) * balanceOf(msg.sender, tokenID)) / 1e18); | ||
if (!tokenIDInitialized && newAmount > 0) { | ||
initialized[tokenID] = true; | ||
} | ||
lastClaimRewardAccrual[msg.sender][tokenID] = newAmount; | ||
} | ||
} |
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,32 @@ | ||
pragma solidity ^0.8.0; | ||
|
||
import "./IPriceFeeds.sol"; | ||
|
||
interface IStakingVault { | ||
event TokensSupported(address[] supportedTokens); | ||
event Deposit(address depositor, address tokenBacked, address depositToken, uint256 amount); | ||
event Withdraw(address withdrawer, address tokenBacked, address receivedToken, uint256 amount); | ||
event PoolDraw(address tokenBacked, address tokenCovered, uint256 amountCovered); | ||
event AddRewards(address tokenBacked, uint256 amount); | ||
event AccumulateRewards(address tokenBacked, uint256 amount); | ||
event DistributeRewards(address tokenBacked, uint256 amount); | ||
|
||
function updateTokenSupport(address[] memory tokens, bool[] memory support, address[] memory listOfSupportedTokens, uint8[] memory decimals) external; | ||
function deposit(address depositToken, address tokenToBack, uint256 amount) external; | ||
function withdraw(address depositToken, address tokenToBack, uint256 amount) external; | ||
function getStoredTokenPrice(uint256 ID) external view returns (uint256); | ||
function getBackingAmount(address token) external view returns (uint256); | ||
function getBackingAmountInNativeTokens(address token) external view returns (uint256[] memory balances); | ||
function convertToID(address depositToken, address tokenToBack) external pure returns (uint256); | ||
function addRewards(address tokenBacked, uint256 rewardAmount) external; | ||
function accumulateRewards(address tokenBacked, uint256 rewardAmount) external; | ||
function distributeRewards(address tokenBacked) external; | ||
function claimRewards(uint256[] memory tokenIDs) external; | ||
// function priceFeed() external view returns (IPriceFeeds); | ||
// function protocol() external view returns (address); | ||
// function valuationToken() external view returns (address); | ||
function stakingTokens() external view returns (address[] memory); | ||
// function rewardToken() external view returns (address); | ||
function drawOnPool(address tokenBacked, address tokenToCover, uint256 amountToCover) external returns (uint256[] memory); | ||
|
||
} |
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.
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.
we have to make a way for the guardian to pause functions from this contract preferably protocol should continue to work afterwards