-
Notifications
You must be signed in to change notification settings - Fork 7
feat: Add ClaimAndStake contract #32
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
Changes from all commits
834502c
ed5e42a
2394332
13c87c5
23e9451
0dbd45b
7af3054
1af5cb6
8ffcf6f
90f982f
0fa124e
c630a94
c6816bc
8c193e2
fcd29da
030b6c6
5f0661f
fe02e96
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 |
|---|---|---|
|
|
@@ -182,26 +182,24 @@ contract AcceleratingDistributor is ReentrancyGuard, Ownable, Multicall { | |
| * @param amount The amount of the token to stake. | ||
| */ | ||
| function stake(address stakedToken, uint256 amount) external nonReentrant onlyEnabled(stakedToken) { | ||
| _updateReward(stakedToken, msg.sender); | ||
|
|
||
| UserDeposit storage userDeposit = stakingTokens[stakedToken].stakingBalances[msg.sender]; | ||
|
|
||
| uint256 averageDepositTime = getAverageDepositTimePostDeposit(stakedToken, msg.sender, amount); | ||
|
|
||
| userDeposit.averageDepositTime = averageDepositTime; | ||
| userDeposit.cumulativeBalance += amount; | ||
| stakingTokens[stakedToken].cumulativeStaked += amount; | ||
|
|
||
| IERC20(stakedToken).safeTransferFrom(msg.sender, address(this), amount); | ||
| _stake(stakedToken, amount, msg.sender); | ||
| } | ||
|
|
||
| emit Stake( | ||
| stakedToken, | ||
| msg.sender, | ||
| amount, | ||
| averageDepositTime, | ||
| userDeposit.cumulativeBalance, | ||
| stakingTokens[stakedToken].cumulativeStaked | ||
| ); | ||
| /** | ||
| * @notice Stake tokens for rewards on behalf of `beneficiary`. | ||
| * @dev The caller of this function must approve this contract to spend amount of stakedToken. | ||
| * @dev The caller of this function is effectively donating their tokens to the beneficiary. The beneficiary | ||
|
Contributor
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. +1 |
||
| * can then unstake or claim rewards as they wish. | ||
| * @param stakedToken The address of the token to stake. | ||
| * @param amount The amount of the token to stake. | ||
| * @param beneficiary User that caller wants to stake on behalf of. | ||
| */ | ||
| function stakeFor( | ||
| address stakedToken, | ||
| uint256 amount, | ||
| address beneficiary | ||
| ) external nonReentrant onlyEnabled(stakedToken) { | ||
| _stake(stakedToken, amount, beneficiary); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -391,4 +389,30 @@ contract AcceleratingDistributor is ReentrancyGuard, Ownable, Multicall { | |
| userDeposit.rewardsAccumulatedPerToken = stakingToken.rewardPerTokenStored; | ||
| } | ||
| } | ||
|
|
||
| function _stake( | ||
| address stakedToken, | ||
| uint256 amount, | ||
| address staker | ||
| ) internal { | ||
| _updateReward(stakedToken, staker); | ||
|
|
||
| UserDeposit storage userDeposit = stakingTokens[stakedToken].stakingBalances[staker]; | ||
|
|
||
| uint256 averageDepositTime = getAverageDepositTimePostDeposit(stakedToken, staker, amount); | ||
|
Contributor
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 is existing code but in getAverageDepositTimePostDeposit, I see uint256 amountWeightedTime = (((amount * 1e18) / (userDeposit.cumulativeBalance + amount)) * Why not multiple first before dividing by (userDeposit.cumulativeBalance + amount) to minimize rounding errors? Also there are way too many parentheses there. uint256 amountWeightedTime = (amount * 1e18) * getTimeSinceAverageDeposit(stakedToken, account) / (userDeposit.cumulativeBalance + amount) / 1e18;
Member
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. These are good points but I don't love modifying existing already-audited code. @chrismaree wdyt?
Member
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 dont think we touch this.
Contributor
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. @kevinuma In general, we would prefer to modify the code as little as possible. The only exception is if we would classify this as a bug. Is the rounding error substantial enough to consider it a bug?
Contributor
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. The rounding error is likely not significant unless a user has staked a significant amount of tokens and then add a very small amount. |
||
|
|
||
| userDeposit.averageDepositTime = averageDepositTime; | ||
| userDeposit.cumulativeBalance += amount; | ||
| stakingTokens[stakedToken].cumulativeStaked += amount; | ||
|
|
||
| IERC20(stakedToken).safeTransferFrom(msg.sender, address(this), amount); | ||
| emit Stake( | ||
| stakedToken, | ||
| staker, | ||
| amount, | ||
| averageDepositTime, | ||
| userDeposit.cumulativeBalance, | ||
| stakingTokens[stakedToken].cumulativeStaked | ||
| ); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| // SPDX-License-Identifier: GPL-3.0-only | ||
| pragma solidity ^0.8.0; | ||
|
|
||
| import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; | ||
| import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
| import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; | ||
| import "@openzeppelin/contracts/utils/Multicall.sol"; | ||
| import "@across-protocol/contracts-v2/contracts/merkle-distributor/AcrossMerkleDistributor.sol"; | ||
| import "./AcceleratingDistributor.sol"; | ||
|
|
||
| /** | ||
| * @notice Allows claimer to claim tokens from AcrossMerkleDistributor and stake into AcceleratingDistributor | ||
| * atomically in a single transaction. This intermediary contract also removes the need for claimer to approve | ||
| * AcceleratingDistributor to spend its staking tokens. | ||
| */ | ||
|
|
||
| contract ClaimAndStake is ReentrancyGuard, Multicall { | ||
| using SafeERC20 for IERC20; | ||
|
|
||
| // Contract which rewards tokens to users that they can then stake. | ||
| AcrossMerkleDistributor public immutable merkleDistributor; | ||
|
|
||
| // Contract that user stakes claimed tokens into. | ||
| AcceleratingDistributor public immutable acceleratingDistributor; | ||
|
|
||
| constructor(AcrossMerkleDistributor _merkleDistributor, AcceleratingDistributor _acceleratingDistributor) { | ||
| merkleDistributor = _merkleDistributor; | ||
| acceleratingDistributor = _acceleratingDistributor; | ||
| } | ||
|
|
||
| /************************************** | ||
| * ADMIN FUNCTIONS * | ||
| **************************************/ | ||
|
|
||
| /** | ||
| * @notice Claim tokens from a MerkleDistributor contract and stake them for rewards in AcceleratingDistributor. | ||
| * @dev Will revert if `merkleDistributor` is not set to valid MerkleDistributor contract. | ||
| * @dev Will revert if the claim recipient account is not equal to caller, or if the reward token | ||
| * for claim is not a valid staking token. | ||
| * @dev Will revert if this contract is not a "whitelisted claimer" on the MerkleDistributor contract. | ||
| * @param _claim Claim leaf to retrieve from MerkleDistributor. | ||
| */ | ||
| function claimAndStake(MerkleDistributorInterface.Claim memory _claim) external nonReentrant { | ||
|
Contributor
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 wonder if we can add a multi claim function as well since merkle distributor supports claimMulti anyway
Member
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. This contract is
Contributor
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. Alright sounds good
Member
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. New claimAndStake function that atomically calls |
||
| require(_claim.account == msg.sender, "claim account not caller"); | ||
| address stakedToken = merkleDistributor.getRewardTokenForWindow(_claim.windowIndex); | ||
| merkleDistributor.claimFor(_claim); | ||
|
Contributor
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. Should we assert that this contract has received the right claim amount for additional safety?
Member
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 don't think we need to since we can assume that
Contributor
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. To call claimFor, this contract has to be whitelisted in the merkle distributor contract: https://github.com/across-protocol/contracts-v2/blob/master/contracts/merkle-distributor/AcrossMerkleDistributor.sol#L41. I don't see this in the deployment step. Is this something we'll do separately?
Member
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. yep! Usually we use the deployment scripts jsut to deploy contracts, and all the setup is done afterwards either manually or via scripts
Contributor
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 thinking it could be a step in the same deployment script as ClaimAndStake if the deployer still has ownership if merkle distributor. It's less manual and ensures that we don't need to make sure the addresses are right later. |
||
| IERC20(stakedToken).safeIncreaseAllowance(address(acceleratingDistributor), _claim.amount); | ||
| acceleratingDistributor.stakeFor(stakedToken, _claim.amount, msg.sender); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| // SPDX-License-Identifier: GPL-3.0-only | ||
| import "@across-protocol/contracts-v2/contracts/merkle-distributor/AcrossMerkleDistributor.sol"; | ||
|
|
||
| pragma solidity ^0.8.0; | ||
|
|
||
| /// @notice Pass through contract that allows tests to access MerkleDistributor from /artifacts via | ||
| // utils.getContractFactory() | ||
| contract MerkleDistributorTest is AcrossMerkleDistributor { | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import "hardhat-deploy"; | ||
| import { HardhatRuntimeEnvironment } from "hardhat/types/runtime"; | ||
|
|
||
| const func = async function (hre: HardhatRuntimeEnvironment) { | ||
| const { deployments, getNamedAccounts } = hre; | ||
| const { deploy } = deployments; | ||
| const { deployer } = await getNamedAccounts(); | ||
|
|
||
| await deploy("AcrossMerkleDistributor", { | ||
| from: deployer, | ||
| log: true, | ||
| skipIfAlreadyDeployed: true, | ||
| args: [], | ||
| }); | ||
| }; | ||
|
|
||
| module.exports = func; | ||
| func.tags = ["AcrossMerkleDistributor", "mainnet"]; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| import "hardhat-deploy"; | ||
| import { HardhatRuntimeEnvironment } from "hardhat/types/runtime"; | ||
|
|
||
| const func = async function (hre: HardhatRuntimeEnvironment) { | ||
| const { deployments, getNamedAccounts } = hre; | ||
| const { deploy } = deployments; | ||
| const { deployer } = await getNamedAccounts(); | ||
|
|
||
| const merkleDistributor = await deployments.get("AcrossMerkleDistributor"); | ||
| const acceleratingDistributor = await deployments.get("AcceleratingDistributor"); | ||
|
|
||
| await deploy("ClaimAndStake", { | ||
| from: deployer, | ||
| log: true, | ||
| skipIfAlreadyDeployed: true, | ||
| args: [merkleDistributor.address, acceleratingDistributor.address], | ||
| }); | ||
| }; | ||
|
|
||
| module.exports = func; | ||
| func.tags = ["ClaimAndStake", "mainnet"]; |
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.
This should be copied exactly to new
_stakeinternal method