Skip to content

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
wants to merge 26 commits into
base: permissionless-listings
Choose a base branch
from
Open
Show file tree
Hide file tree
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 Nov 16, 2022
f84c1dc
Merge remote-tracking branch 'origin/permissionless-listings' into pl…
Drypto13 Nov 23, 2022
95bf274
fix issues
Drypto13 Nov 26, 2022
eed501a
add test case
Drypto13 Dec 1, 2022
0864a22
full test case for PL staking vault
Drypto13 Dec 10, 2022
841fb69
added events and minor optimization
Drypto13 Dec 11, 2022
d5cb5a9
Create IStakingVault.sol
Drypto13 Dec 11, 2022
2b7d366
Merge branch 'permissionless-listings' into pl-staking-vault
Drypto13 Dec 22, 2022
0c80b7b
Update LoanTokenLogicStandard.sol
Drypto13 Dec 22, 2022
943bde3
Update HelperImpl.sol
Drypto13 Dec 22, 2022
8e273a5
Update DAppHelper.sol
Drypto13 Dec 22, 2022
0c83b2d
Update IToken.sol
Drypto13 Dec 22, 2022
90412f7
Merge branch 'permissionless-listings' into pl-staking-vault
Drypto13 Jan 12, 2023
586856d
Update staking-vault.py
Drypto13 Jan 12, 2023
d3b1bf6
Minor changes
Drypto13 Jan 27, 2023
e2cca1b
Merge branch 'permissionless-listings' into pl-staking-vault
0xosama Feb 1, 2023
e02b411
format
0xosama Feb 1, 2023
78d6f29
restricted to view
0xosama Feb 1, 2023
1f24733
Merge branch 'permissionless-listings' into pl-staking-vault
0xosama Feb 3, 2023
2d823e7
changes
Drypto13 Feb 20, 2023
028c1c6
constants uppercase
0xosama Feb 20, 2023
22e5208
made constants public
0xosama Feb 20, 2023
da568a1
remove func from interface
0xosama Feb 20, 2023
ba0284b
renamed variable to make it consistent with other namings
0xosama Feb 20, 2023
ef85ed3
Merge branch 'permissionless-listings' into pl-staking-vault
0xosama Feb 22, 2023
37bffdf
load bool to memory
Drypto13 Feb 23, 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
11 changes: 6 additions & 5 deletions brownie-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
267 changes: 267 additions & 0 deletions contracts/staking-vault/StakingVault.sol
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 {
Copy link
Contributor

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

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;
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() {
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)) {
_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; ) {
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];
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;
}
}
32 changes: 32 additions & 0 deletions interfaces/IStakingVault.sol
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);

}
Loading