diff --git a/contracts/Addresses.sol b/contracts/Addresses.sol new file mode 100644 index 0000000..f522559 --- /dev/null +++ b/contracts/Addresses.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.9; + +contract Addresses { + address public constant duck = address(0x92E187a03B6CD19CB6AF293ba17F2745Fd2357D5); + address public constant veDuck = address(0x48DdD27a4d54CD3e8c34F34F7e66e998442DBcE3); +} \ No newline at end of file diff --git a/contracts/BaseRewardPool.sol b/contracts/BaseRewardPool.sol deleted file mode 100644 index 08abfd0..0000000 --- a/contracts/BaseRewardPool.sol +++ /dev/null @@ -1,334 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.9; -/** - *Submitted for verification at Etherscan.io on 2020-07-17 - */ - -/* - ____ __ __ __ _ - / __/__ __ ___ / /_ / / ___ / /_ (_)__ __ - _\ \ / // // _ \/ __// _ \/ -_)/ __// / \ \ / -/___/ \_, //_//_/\__//_//_/\__/ \__//_/ /_\_\ - /___/ -* Synthetix: BaseRewardPool.sol -* -* Docs: https://docs.synthetix.io/ -* -* -* MIT License -* =========== -* -* Copyright (c) 2020 Synthetix -* -* Permission is hereby granted, free of charge, to any person obtaining a copy -* of this software and associated documentation files (the "Software"), to deal -* in the Software without restriction, including without limitation the rights -* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the Software is -* furnished to do so, subject to the following conditions: -* -* The above copyright notice and this permission notice shall be included in all -* copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -*/ - -import "./Interfaces.sol"; -import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; -import '@openzeppelin/contracts/utils/math/SafeMath.sol'; -import '@openzeppelin/contracts/utils/math/Math.sol'; -import '@openzeppelin/contracts/utils/Address.sol'; -import '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; - - - -contract BaseRewardPool { - using SafeMath for uint256; - using SafeERC20 for IERC20; - - IERC20 public rewardToken; - IERC20 public stakingToken; - uint256 public constant duration = 7 days; - - address public operator; - address public rewardManager; - - uint256 public pid; - uint256 public periodFinish = 0; - uint256 public rewardRate = 0; - uint256 public lastUpdateTime; - uint256 public rewardPerTokenStored; - uint256 public queuedRewards = 0; - uint256 public currentRewards = 0; - uint256 public historicalRewards = 0; - uint256 public constant newRewardRatio = 830; - uint256 private _totalSupply; - mapping(address => uint256) public userRewardPerTokenPaid; - mapping(address => uint256) public rewards; - mapping(address => uint256) private _balances; - - address[] public extraRewards; - - event RewardAdded(uint256 reward); - event Staked(address indexed user, uint256 amount); - event Withdrawn(address indexed user, uint256 amount); - event RewardPaid(address indexed user, uint256 reward); - - constructor( - uint256 pid_, - address stakingToken_, - address rewardToken_, - address operator_, - address rewardManager_ - ) { - pid = pid_; - stakingToken = IERC20(stakingToken_); - rewardToken = IERC20(rewardToken_); - operator = operator_; - rewardManager = rewardManager_; - } - - function totalSupply() public view returns (uint256) { - return _totalSupply; - } - - function balanceOf(address account) public view returns (uint256) { - return _balances[account]; - } - - function extraRewardsLength() external view returns (uint256) { - return extraRewards.length; - } - - function addExtraReward(address _reward) external returns(bool){ - require(msg.sender == rewardManager, "!authorized"); - require(_reward != address(0),"!reward setting"); - - extraRewards.push(_reward); - return true; - } - function clearExtraRewards() external{ - require(msg.sender == rewardManager, "!authorized"); - delete extraRewards; - } - - modifier updateReward(address account) { - rewardPerTokenStored = rewardPerToken(); - lastUpdateTime = lastTimeRewardApplicable(); - if (account != address(0)) { - rewards[account] = earned(account); - userRewardPerTokenPaid[account] = rewardPerTokenStored; - } - _; - } - - function lastTimeRewardApplicable() public view returns (uint256) { - return Math.min(block.timestamp, periodFinish); - } - - function rewardPerToken() public view returns (uint256) { - if (totalSupply() == 0) { - return rewardPerTokenStored; - } - return - rewardPerTokenStored.add( - lastTimeRewardApplicable() - .sub(lastUpdateTime) - .mul(rewardRate) - .mul(1e18) - .div(totalSupply()) - ); - } - - function earned(address account) public view returns (uint256) { - return - balanceOf(account) - .mul(rewardPerToken().sub(userRewardPerTokenPaid[account])) - .div(1e18) - .add(rewards[account]); - } - - function stake(uint256 _amount) - public - updateReward(msg.sender) - returns(bool) - { - require(_amount > 0, 'RewardPool : Cannot stake 0'); - - //also stake to linked rewards - for(uint i=0; i < extraRewards.length; i++){ - IRewards(extraRewards[i]).stake(msg.sender, _amount); - } - - _totalSupply = _totalSupply.add(_amount); - _balances[msg.sender] = _balances[msg.sender].add(_amount); - - stakingToken.safeTransferFrom(msg.sender, address(this), _amount); - emit Staked(msg.sender, _amount); - - - return true; - } - - function stakeAll() external returns(bool){ - uint256 balance = stakingToken.balanceOf(msg.sender); - stake(balance); - return true; - } - - function stakeFor(address _for, uint256 _amount) - public - updateReward(_for) - returns(bool) - { - require(_amount > 0, 'RewardPool : Cannot stake 0'); - - //also stake to linked rewards - for(uint i=0; i < extraRewards.length; i++){ - IRewards(extraRewards[i]).stake(_for, _amount); - } - - //give to _for - _totalSupply = _totalSupply.add(_amount); - _balances[_for] = _balances[_for].add(_amount); - - //take away from sender - stakingToken.safeTransferFrom(msg.sender, address(this), _amount); - emit Staked(_for, _amount); - - return true; - } - - - function withdraw(uint256 amount, bool claim) - public - updateReward(msg.sender) - returns(bool) - { - require(amount > 0, 'RewardPool : Cannot withdraw 0'); - - //also withdraw from linked rewards - for(uint i=0; i < extraRewards.length; i++){ - IRewards(extraRewards[i]).withdraw(msg.sender, amount); - } - - _totalSupply = _totalSupply.sub(amount); - _balances[msg.sender] = _balances[msg.sender].sub(amount); - - stakingToken.safeTransfer(msg.sender, amount); - emit Withdrawn(msg.sender, amount); - - if(claim){ - getReward(msg.sender,true); - } - - return true; - } - - function withdrawAll(bool claim) external{ - withdraw(_balances[msg.sender],claim); - } - - function withdrawAndUnwrap(uint256 amount, bool claim) public updateReward(msg.sender) returns(bool){ - - //also withdraw from linked rewards - for(uint i=0; i < extraRewards.length; i++){ - IRewards(extraRewards[i]).withdraw(msg.sender, amount); - } - - _totalSupply = _totalSupply.sub(amount); - _balances[msg.sender] = _balances[msg.sender].sub(amount); - - //tell operator to withdraw from here directly to user - IDeposit(operator).withdrawTo(pid,amount,msg.sender); - emit Withdrawn(msg.sender, amount); - - //get rewards too - if(claim){ - getReward(msg.sender,true); - } - return true; - } - - function withdrawAllAndUnwrap(bool claim) external{ - withdrawAndUnwrap(_balances[msg.sender],claim); - } - - function getReward(address _account, bool _claimExtras) public updateReward(_account) returns(bool){ - uint256 reward = earned(_account); - if (reward > 0) { - rewards[_account] = 0; - rewardToken.safeTransfer(_account, reward); - emit RewardPaid(_account, reward); - } - - //also get rewards from linked rewards - if(_claimExtras){ - for(uint i=0; i < extraRewards.length; i++){ - IRewards(extraRewards[i]).getReward(_account); - } - } - return true; - } - - function getReward() external returns(bool){ - getReward(msg.sender,true); - return true; - } - - function donate(uint256 _amount) external returns(bool){ - IERC20(rewardToken).safeTransferFrom(msg.sender, address(this), _amount); - queuedRewards = queuedRewards.add(_amount); - } - - function queueNewRewards(uint256 _rewards) external returns(bool){ - require(msg.sender == operator, "!authorized"); - - _rewards = _rewards.add(queuedRewards); - - if (block.timestamp >= periodFinish) { - notifyRewardAmount(_rewards); - queuedRewards = 0; - return true; - } - - //et = now - (finish-duration) - uint256 elapsedTime = block.timestamp.sub(periodFinish.sub(duration)); - //current at now: rewardRate * elapsedTime - uint256 currentAtNow = rewardRate * elapsedTime; - uint256 queuedRatio = currentAtNow.mul(1000).div(_rewards); - - //uint256 queuedRatio = currentRewards.mul(1000).div(_rewards); - if(queuedRatio < newRewardRatio){ - notifyRewardAmount(_rewards); - queuedRewards = 0; - }else{ - queuedRewards = _rewards; - } - return true; - } - - function notifyRewardAmount(uint256 reward) - internal - updateReward(address(0)) - { - historicalRewards = historicalRewards.add(reward); - if (block.timestamp >= periodFinish) { - rewardRate = reward.div(duration); - } else { - uint256 remaining = periodFinish.sub(block.timestamp); - uint256 leftover = remaining.mul(rewardRate); - reward = reward.add(leftover); - rewardRate = reward.div(duration); - } - currentRewards = reward; - lastUpdateTime = block.timestamp; - periodFinish = block.timestamp.add(duration); - emit RewardAdded(reward); - } -} \ No newline at end of file diff --git a/contracts/Booster.sol b/contracts/Booster.sol index e7b27c8..59fadfa 100644 --- a/contracts/Booster.sol +++ b/contracts/Booster.sol @@ -2,61 +2,34 @@ pragma solidity 0.8.9; import "./Interfaces.sol"; +import "./FeePool.sol"; import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; import '@openzeppelin/contracts/utils/Address.sol'; import '@openzeppelin/contracts/utils/math/SafeMath.sol'; import '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; +import '@openzeppelin/contracts/security/ReentrancyGuard.sol'; -contract Booster { +contract Booster is ReentrancyGuard { using SafeMath for uint; using SafeERC20 for IERC20; using Address for address; - address public constant crv = address(0xD533a949740bb3306d119CC777fa900bA034cd52); - address public constant registry = address(0x0000000022D53366457F9d5E68Ec105046FC4383); - uint256 public constant distributionAddressId = 4; - address public constant voteOwnership = address(0xE478de485ad2fe566d49342Cbd03E49ed7DB3356); - address public constant voteParameter = address(0xBCfF8B0b9419b9A88c44546519b1e909cF330399); - - uint256 public duckStakerIncentive = 0; //incentive to native token stakers uint256 public earmarkIncentive = 100; //incentive to users who spend gas to make calls - uint256 public platformFee = 0; //possible fee to build treasury - uint256 public constant MaxFees = 2000; + uint256 public platformFee = 1000; //possible fee to build treasury + uint256 public constant MaxFees = 5000; uint256 public constant FEE_DENOMINATOR = 10000; address public owner; address public feeManager; - address public poolManager; address public immutable staker; - address public rewardFactory; - address public stashFactory; - address public tokenFactory; - address public rewardArbitrator; - address public voteDelegate; address public treasury; - address public stakerRewards; // CRV for DUCK - address public lockFees; // 3CRV for uveCRV, no additional fees applied + address public lockFees; // usdp for uveDUCK, no additional fees applied address public feeDistro; address public feeToken; bool public isShutdown; - struct PoolInfo { - address lptoken; - address token; - address gauge; - address crvRewards; - address stash; - bool shutdown; - } - - //index(pid) -> pool - PoolInfo[] public poolInfo; - mapping(address => bool) public gaugeMap; - - address public preDepositChecker; - event Deposited(address indexed user, uint256 indexed poolid, uint256 amount); event Withdrawn(address indexed user, uint256 indexed poolid, uint256 amount); @@ -65,11 +38,9 @@ contract Booster { isShutdown = false; staker = _staker; owner = msg.sender; - voteDelegate = msg.sender; feeManager = msg.sender; - poolManager = msg.sender; - feeDistro = address(0); //address(0xA464e6DCda8AC41e03616F95f4BC98a13b8922Dc); - feeToken = address(0); //address(0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490); + feeDistro = address(0); + feeToken = address(0); treasury = address(0); } @@ -86,66 +57,25 @@ contract Booster { feeManager = _feeM; } - function setPoolManager(address _poolM) external { - require(msg.sender == poolManager, "!auth"); - poolManager = _poolM; - } - - function setFactories(address _rfactory, address _sfactory, address _tfactory) external { - require(msg.sender == owner, "!auth"); - - //reward factory only allow this to be called once even if owner - //removes ability to inject malicious staking contracts - //token factory can also be immutable - if (rewardFactory == address(0)) { - rewardFactory = _rfactory; - tokenFactory = _tfactory; - } - - //stash factory should be considered more safe to change - //updating may be required to handle new types of gauges - stashFactory = _sfactory; - } - - function setArbitrator(address _arb) external { - require(msg.sender == owner, "!auth"); - rewardArbitrator = _arb; - } - - function setVoteDelegate(address _voteDelegate) external { - require(msg.sender == voteDelegate, "!auth"); - voteDelegate = _voteDelegate; - } - - function setRewardContract(address _stakerRewards) external { - require(msg.sender == owner, "!auth"); - stakerRewards = _stakerRewards; - } - - // Set reward token and claim contract, get from Curve's registry - function setFeeInfo() external { + function setFeeInfo(address _feeDistro, address _uveduck) external { require(msg.sender == feeManager, "!auth"); - feeDistro = IRegistry(registry).get_address(distributionAddressId); - address _feeToken = IFeeDistro(feeDistro).token(); + feeDistro = _feeDistro; + address _feeToken = IFeeDistro(_feeDistro).token(); if (feeToken != _feeToken) { //create a new reward contract for the new token - lockFees = IRewardFactory(rewardFactory).CreateFeePool(_feeToken, address(this)); + lockFees = address(new FeePool(_uveduck, _feeToken, address(this))); feeToken = _feeToken; } } - function setFees(uint256 _duckStakerFees, uint256 _callerFees, uint256 _platform) external { + function setFees(uint256 _callerFees, uint256 _platform) external { require(msg.sender == feeManager, "!auth"); - uint256 total = _duckStakerFees.add(_callerFees).add(_platform); + uint256 total = _callerFees.add(_platform); require(total <= MaxFees, ">MaxFees"); + require(_callerFees >= 1 && _callerFees <= 100, "values must be within certain ranges"); - require(_duckStakerFees <= 1990 - && _callerFees >= 10 && _callerFees <= 100 - && _platform <= 1990, "values must be within certain ranges"); - - duckStakerIncentive = _duckStakerFees; earmarkIncentive = _callerFees; platformFee = _platform; } @@ -157,293 +87,35 @@ contract Booster { /// END SETTER SECTION /// - - function poolLength() external view returns (uint256) { - return poolInfo.length; - } - - function setPreDepositChecker(address _checker) external { - require(msg.sender == poolManager && !isShutdown, "!add"); - preDepositChecker = _checker; - } - - //create a new pool - function addPool(address _lptoken, address _gauge, uint256 _stashVersion) external returns(bool) { - require(msg.sender == poolManager && !isShutdown, "!add"); - require(_gauge != address(0) && _lptoken != address(0), "!param"); - - //the next pool's pid - uint256 pid = poolInfo.length; - - //create a tokenized deposit - address token = ITokenFactory(tokenFactory).CreateDepositToken(_lptoken); - //create a reward contract for crv rewards - address newRewardPool = IRewardFactory(rewardFactory).CreateCrvRewards(pid, token); - //create a stash to handle extra incentives - address stash = IStashFactory(stashFactory).CreateStash(pid, _gauge, staker, _stashVersion); - - //add the new pool - poolInfo.push( - PoolInfo({ - lptoken: _lptoken, - token: token, - gauge: _gauge, - crvRewards: newRewardPool, - stash: stash, - shutdown: false - }) - ); - gaugeMap[_gauge] = true; - //give stashes access to rewardfactory and voteproxy - // voteproxy so it can grab the incentive tokens off the contract after claiming rewards - // reward factory so that stashes can make new extra reward contracts if a new incentive is added to the gauge - if (stash != address(0)) { - IStaker(staker).setStashAccess(stash, true); - IRewardFactory(rewardFactory).setAccess(stash, true); - } - return true; - } - - //shutdown pool - function shutdownPool(uint256 _pid) external returns(bool) { - require(msg.sender == poolManager, "!auth"); - PoolInfo storage pool = poolInfo[_pid]; - - //withdraw from gauge - try IStaker(staker).withdrawAll(pool.lptoken, pool.gauge) { - }catch{} - - pool.shutdown = true; - gaugeMap[pool.gauge] = false; - return true; - } - //shutdown this contract. // unstake and pull all lp tokens to this address // only allow withdrawals function shutdownSystem() external { require(msg.sender == owner, "!auth"); isShutdown = true; - - for(uint i = 0; i < poolInfo.length; i++) { - PoolInfo storage pool = poolInfo[i]; - if (pool.shutdown) continue; - - address token = pool.lptoken; - address gauge = pool.gauge; - - //withdraw from gauge - try IStaker(staker).withdrawAll(token, gauge) { - pool.shutdown = true; - }catch{} - } - } - - - //deposit lp tokens and stake - function deposit(uint256 _pid, uint256 _amount, bool _stake) public returns(bool) { - require(!isShutdown, "shutdown"); - PoolInfo storage pool = poolInfo[_pid]; - require(pool.shutdown == false, "pool is closed"); - - address lptoken = pool.lptoken; - address token = pool.token; - - if (preDepositChecker != address(0)) { - require(IPreDepositChecker(preDepositChecker).canDeposit(msg.sender, _pid, _amount), "check failed"); - } - - //send to proxy to stake - IERC20(lptoken).safeTransferFrom(msg.sender, staker, _amount); - - //stake - address gauge = pool.gauge; - require(gauge != address(0), "!gauge setting"); - IStaker(staker).deposit(lptoken, gauge); - - //some gauges claim rewards when depositing, stash them in a seperate contract until next claim - address stash = pool.stash; - if (stash != address(0)) { - IStash(stash).stashRewards(); - } - - if (_stake) { - //mint here and send to rewards on user behalf - ITokenMinter(token).mint(address(this), _amount); - address rewardContract = pool.crvRewards; - IERC20(token).safeApprove(rewardContract, 0); - IERC20(token).safeApprove(rewardContract, _amount); - IRewards(rewardContract).stakeFor(msg.sender, _amount); - } else { - //add user balance directly - ITokenMinter(token).mint(msg.sender, _amount); - } - - - emit Deposited(msg.sender, _pid, _amount); - return true; } - //deposit all lp tokens and stake - function depositAll(uint256 _pid, bool _stake) external returns(bool) { - address lptoken = poolInfo[_pid].lptoken; - uint256 balance = IERC20(lptoken).balanceOf(msg.sender); - deposit(_pid,balance,_stake); - return true; - } - - //withdraw lp tokens - function _withdraw(uint256 _pid, uint256 _amount, address _from, address _to) internal { - PoolInfo storage pool = poolInfo[_pid]; - address lptoken = pool.lptoken; - address gauge = pool.gauge; - - //remove lp balance - address token = pool.token; - ITokenMinter(token).burn(_from, _amount); - - //pull from gauge if not shutdown - // if shutdown tokens will be in this contract - if (!pool.shutdown) { - IStaker(staker).withdraw(lptoken, gauge, _amount); - } - - //some gauges claim rewards when withdrawing, stash them in a seperate contract until next claim - //do not call if shutdown since stashes wont have access - address stash = pool.stash; - if (stash != address(0) && !isShutdown && !pool.shutdown) { - IStash(stash).stashRewards(); - } - - //return lp tokens - IERC20(lptoken).safeTransfer(_to, _amount); - - emit Withdrawn(_to, _pid, _amount); - } - - //withdraw lp tokens - function withdraw(uint256 _pid, uint256 _amount) public returns(bool) { - _withdraw(_pid, _amount, msg.sender, msg.sender); - return true; - } - - //withdraw all lp tokens - function withdrawAll(uint256 _pid) public returns(bool) { - address token = poolInfo[_pid].token; - uint256 userBal = IERC20(token).balanceOf(msg.sender); - withdraw(_pid, userBal); - return true; - } - - //allow reward contracts to send here and withdraw to user - function withdrawTo(uint256 _pid, uint256 _amount, address _to) external returns(bool) { - address rewardContract = poolInfo[_pid].crvRewards; - require(msg.sender == rewardContract, "!auth"); - - _withdraw(_pid,_amount,msg.sender,_to); - return true; - } - - - //delegate address votes on dao - function vote(uint256 _voteId, address _votingAddress, bool _support) external returns(bool) { - require(msg.sender == voteDelegate, "!auth"); - require(_votingAddress == voteOwnership || _votingAddress == voteParameter, "!voteAddr"); - - IStaker(staker).vote(_voteId,_votingAddress,_support); - return true; - } - - function voteGaugeWeight(address[] calldata _gauge, uint256[] calldata _weight) external returns(bool) { - require(msg.sender == voteDelegate, "!auth"); - - for(uint256 i = 0; i < _gauge.length; i++) { - IStaker(staker).voteGaugeWeight(_gauge[i],_weight[i]); - } - return true; - } - - function claimRewards(uint256 _pid, address _gauge) external returns(bool) { - address stash = poolInfo[_pid].stash; - require(msg.sender == stash, "!auth"); - - IStaker(staker).claimRewards(_gauge); - return true; - } - - function setGaugeRedirect(uint256 _pid) external returns(bool) { - address stash = poolInfo[_pid].stash; - require(msg.sender == stash, "!auth"); - address gauge = poolInfo[_pid].gauge; - bytes memory data = abi.encodeWithSelector(bytes4(keccak256("set_rewards_receiver(address)")), stash); - IStaker(staker).execute(gauge, uint256(0), data); - return true; - } - - //claim crv and extra rewards and disperse to reward contracts - function _earmarkRewards(uint256 _pid) internal { - PoolInfo storage pool = poolInfo[_pid]; - require(pool.shutdown == false, "pool is closed"); - - address gauge = pool.gauge; - - //claim crv - IStaker(staker).claimCrv(gauge); - - //check if there are extra rewards - address stash = pool.stash; - if (stash != address(0)) { - //claim extra rewards - IStash(stash).claimRewards(); - //process extra rewards - IStash(stash).processStash(); + //claim fees from distro contract, put in lockers' reward contract + function earmarkFees() external nonReentrant returns(bool) { + //claim fee rewards + IStaker(staker).claimFees(feeDistro, feeToken); + //send fee rewards to reward contract + uint256 _balance = IERC20(feeToken).balanceOf(address(this)); + if (_balance == 0) { + return true; } - //crv balance - uint256 crvBal = IERC20(crv).balanceOf(address(this)); - - if (crvBal > 0) { - uint256 _stakerIncentive = stakerRewards == address(0) ? 0 : crvBal.mul(duckStakerIncentive).div(FEE_DENOMINATOR); - uint256 _callIncentive = crvBal.mul(earmarkIncentive).div(FEE_DENOMINATOR); - - //send treasury - if (treasury != address(0) && treasury != address(this) && platformFee > 0) { - //only subtract after address condition check - uint256 _platform = crvBal.mul(platformFee).div(FEE_DENOMINATOR); - crvBal = crvBal.sub(_platform); - IERC20(crv).safeTransfer(treasury, _platform); - } - - //remove incentives from balance - crvBal = crvBal.sub(_callIncentive).sub(_stakerIncentive); - - //send incentives for calling - IERC20(crv).safeTransfer(msg.sender, _callIncentive); - - //send crv to lp provider reward contract - address rewardContract = pool.crvRewards; - IERC20(crv).safeTransfer(rewardContract, crvBal); - IRewards(rewardContract).queueNewRewards(crvBal); + uint256 callIncentive = _balance.mul(earmarkIncentive).div(FEE_DENOMINATOR); - if (_stakerIncentive != 0) { - //send DUCK-stakers' share of crv to reward contract - IERC20(crv).safeTransfer(stakerRewards, _stakerIncentive); - } + if (platformFee > 0 && treasury != address(0)) { + uint fee = _balance.mul(platformFee).div(FEE_DENOMINATOR); + IERC20(feeToken).safeTransfer(treasury, fee); + _balance = _balance.sub(fee); } - } - function earmarkRewards(uint256 _pid) external returns (bool) { - require(!isShutdown, "shutdown"); - _earmarkRewards(_pid); - return true; - } + _balance = _balance.sub(callIncentive); + IERC20(feeToken).safeTransfer(msg.sender, callIncentive); - //claim fees from curve distro contract, put in lockers' reward contract - function earmarkFees() external returns(bool) { - //claim fee rewards - IStaker(staker).claimFees(feeDistro, feeToken); - //send fee rewards to reward contract - uint256 _balance = IERC20(feeToken).balanceOf(address(this)); IERC20(feeToken).safeTransfer(lockFees, _balance); IRewards(lockFees).queueNewRewards(_balance); return true; diff --git a/contracts/CurveVoterProxy.sol b/contracts/CurveVoterProxy.sol deleted file mode 100644 index ed8df40..0000000 --- a/contracts/CurveVoterProxy.sol +++ /dev/null @@ -1,203 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.9; - -import "./Interfaces.sol"; -import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; -import '@openzeppelin/contracts/utils/Address.sol'; -import '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; -import '@openzeppelin/contracts/utils/math/SafeMath.sol'; - - -contract CurveVoterProxy { - using SafeMath for uint; - using SafeERC20 for IERC20; - using Address for address; - - address public constant mintr = address(0xd061D61a4d941c39E5453435B6345Dc261C2fcE0); - address public constant crv = address(0xD533a949740bb3306d119CC777fa900bA034cd52); - - address public constant escrow = address(0x5f3b5DfEb7B28CDbD7FAba78963EE202a494e2A2); - address public constant gaugeController = address(0x2F50D538606Fa9EDD2B11E2446BEb18C9D5846bB); - - address public owner; - address public operator; // Booster - address public depositor; - - mapping (address => bool) private stashPool; - mapping (address => bool) private protectedTokens; - - constructor() { - owner = msg.sender; - } - - function getName() external pure returns (string memory) { - return "CurveVoterProxy"; - } - - function setOwner(address _owner) external { - require(msg.sender == owner, "!auth"); - owner = _owner; - } - - function setOperator(address _operator) external { - require(msg.sender == owner, "!auth"); - require(operator == address(0) || IDeposit(operator).isShutdown() == true, "needs shutdown"); - - operator = _operator; - } - - function setDepositor(address _depositor) external { - require(msg.sender == owner, "!auth"); - - depositor = _depositor; - } - - function setStashAccess(address _stash, bool _status) external returns(bool) { - require(msg.sender == operator, "!auth"); - if (_stash != address(0)) { - stashPool[_stash] = _status; - } - return true; - } - - - function deposit(address _token, address _gauge) external returns(bool) { - require(msg.sender == operator, "!auth"); - if (protectedTokens[_token] == false) { - protectedTokens[_token] = true; - } - if (protectedTokens[_gauge] == false) { - protectedTokens[_gauge] = true; - } - uint256 balance = IERC20(_token).balanceOf(address(this)); - if (balance > 0) { - IERC20(_token).safeApprove(_gauge, 0); - IERC20(_token).safeApprove(_gauge, balance); - ICurveGauge(_gauge).deposit(balance); - } - return true; - } - - //stash only function for pulling extra incentive reward tokens out - function withdraw(IERC20 _asset) external returns (uint256 balance) { - require(stashPool[msg.sender] == true, "!auth"); - - //check protection - if (protectedTokens[address(_asset)] == true) { - return 0; - } - - balance = _asset.balanceOf(address(this)); - _asset.safeTransfer(msg.sender, balance); - return balance; - } - - // Withdraw partial funds - function withdraw(address _token, address _gauge, uint256 _amount) public returns(bool) { - require(msg.sender == operator, "!auth"); - uint256 _balance = IERC20(_token).balanceOf(address(this)); - if (_balance < _amount) { - _amount = _withdrawSome(_gauge, _amount.sub(_balance)); - _amount = _amount.add(_balance); - } - IERC20(_token).safeTransfer(msg.sender, _amount); - return true; - } - - function withdrawAll(address _token, address _gauge) external returns(bool) { - require(msg.sender == operator, "!auth"); - uint256 amount = balanceOfPool(_gauge).add(IERC20(_token).balanceOf(address(this))); - withdraw(_token, _gauge, amount); - return true; - } - - function _withdrawSome(address _gauge, uint256 _amount) internal returns (uint256) { - ICurveGauge(_gauge).withdraw(_amount); - return _amount; - } - - function createLock(uint256 _value, uint256 _unlockTime) external returns(bool) { - require(msg.sender == depositor, "!auth"); - IERC20(crv).safeApprove(escrow, 0); - IERC20(crv).safeApprove(escrow, _value); - ICurveVoteEscrow(escrow).create_lock(_value, _unlockTime); - return true; - } - - function increaseAmount(uint256 _value) external returns(bool) { - require(msg.sender == depositor, "!auth"); - IERC20(crv).safeApprove(escrow, 0); - IERC20(crv).safeApprove(escrow, _value); - ICurveVoteEscrow(escrow).increase_amount(_value); - return true; - } - - function increaseTime(uint256 _value) external returns(bool) { - require(msg.sender == depositor, "!auth"); - ICurveVoteEscrow(escrow).increase_unlock_time(_value); - return true; - } - - function release() external returns(bool) { - require(msg.sender == depositor, "!auth"); - ICurveVoteEscrow(escrow).withdraw(); - return true; - } - - function vote(uint256 _voteId, address _votingAddress, bool _support) external returns(bool) { - require(msg.sender == operator, "!auth"); - IVoting(_votingAddress).vote(_voteId, _support, false); - return true; - } - - function voteGaugeWeight(address _gauge, uint256 _weight) external returns(bool) { - require(msg.sender == operator, "!auth"); - - //vote - IVoting(gaugeController).vote_for_gauge_weights(_gauge, _weight); - return true; - } - - function claimCrv(address _gauge) external returns (uint256) { - require(msg.sender == operator, "!auth"); - - uint256 _balance = 0; - try IMinter(mintr).mint(_gauge) { - _balance = IERC20(crv).balanceOf(address(this)); - IERC20(crv).safeTransfer(operator, _balance); - } catch {} - - return _balance; - } - - function claimRewards(address _gauge) external returns(bool) { - require(msg.sender == operator, "!auth"); - ICurveGauge(_gauge).claim_rewards(); - return true; - } - - function claimFees(address _distroContract, address _token) external returns (uint256) { - require(msg.sender == operator, "!auth"); - IFeeDistro(_distroContract).claim(); - uint256 _balance = IERC20(_token).balanceOf(address(this)); - IERC20(_token).safeTransfer(operator, _balance); - return _balance; - } - - function balanceOfPool(address _gauge) public view returns (uint256) { - return ICurveGauge(_gauge).balanceOf(address(this)); - } - - function execute( - address _to, - uint256 _value, - bytes calldata _data - ) external returns (bool, bytes memory) { - require(msg.sender == operator, "!auth"); - - (bool success, bytes memory result) = _to.call{value:_value}(_data); - - return (success, result); - } - -} \ No newline at end of file diff --git a/contracts/DepositToken.sol b/contracts/DepositToken.sol deleted file mode 100644 index bc2a561..0000000 --- a/contracts/DepositToken.sol +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.9; - -import "./Interfaces.sol"; -import '@openzeppelin/contracts/token/ERC20/ERC20.sol'; - - -contract DepositToken is ERC20 { - - address public operator; - - constructor(address _operator, address _lptoken) - ERC20( - string( - abi.encodePacked(ERC20(_lptoken).name()," Unit Protocol Deposit") - ), - string(abi.encodePacked("up", ERC20(_lptoken).symbol())) - ) - { - operator = _operator; - } - - function mint(address _to, uint256 _amount) external { - require(msg.sender == operator, "!authorized"); - - _mint(_to, _amount); - } - - function burn(address _from, uint256 _amount) external { - require(msg.sender == operator, "!authorized"); - - _burn(_from, _amount); - } -} \ No newline at end of file diff --git a/contracts/CrvDepositor.sol b/contracts/DuckDepositor.sol similarity index 72% rename from contracts/CrvDepositor.sol rename to contracts/DuckDepositor.sol index 43b26de..a77eb03 100644 --- a/contracts/CrvDepositor.sol +++ b/contracts/DuckDepositor.sol @@ -2,17 +2,17 @@ pragma solidity 0.8.9; import './Interfaces.sol'; +import "./Addresses.sol"; import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; import '@openzeppelin/contracts/utils/Address.sol'; import '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; -contract CrvDepositor { +contract DuckDepositor is Addresses { using SafeERC20 for IERC20; using Address for address; - address public constant crv = address(0xD533a949740bb3306d119CC777fa900bA034cd52); - address public constant escrow = address(0x5f3b5DfEb7B28CDbD7FAba78963EE202a494e2A2); + address public constant escrow = veDuck; uint256 private constant MAXTIME = 4 * 364 * 86400; uint256 private constant WEEK = 7 * 86400; @@ -37,35 +37,35 @@ contract CrvDepositor { function initialLock() external { require(msg.sender == feeManager, "!auth"); - uint256 vecrv = IERC20(escrow).balanceOf(staker); - if (vecrv == 0) { + uint256 veduck = IERC20(escrow).balanceOf(staker); + if (veduck == 0) { uint256 unlockAt = block.timestamp + MAXTIME; uint256 unlockInWeeks = (unlockAt / WEEK) * WEEK; //release old lock if exists IStaker(staker).release(); //create new lock - uint256 crvBalanceStaker = IERC20(crv).balanceOf(staker); - IStaker(staker).createLock(crvBalanceStaker, unlockAt); + uint256 duckBalanceStaker = IERC20(duck).balanceOf(staker); + IStaker(staker).createLock(duckBalanceStaker, unlockAt); unlockTime = unlockInWeeks; } } //lock curve function _lockCurve() internal { - uint256 crvBalance = IERC20(crv).balanceOf(address(this)); - if (crvBalance > 0) { - IERC20(crv).safeTransfer(staker, crvBalance); + uint256 duckBalance = IERC20(duck).balanceOf(address(this)); + if (duckBalance > 0) { + IERC20(duck).safeTransfer(staker, duckBalance); } //increase ammount - uint256 crvBalanceStaker = IERC20(crv).balanceOf(staker); - if (crvBalanceStaker == 0) { + uint256 duckBalanceStaker = IERC20(duck).balanceOf(staker); + if (duckBalanceStaker == 0) { return; } //increase amount - IStaker(staker).increaseAmount(crvBalanceStaker); + IStaker(staker).increaseAmount(duckBalanceStaker); uint256 unlockAt = block.timestamp + MAXTIME; @@ -82,12 +82,12 @@ contract CrvDepositor { _lockCurve(); } - //deposit crv for cvxCrv + //deposit duck for uveDuck and stake to _stakeAddress (suveDuck) function deposit(uint256 _amount, address _stakeAddress) public { require(_amount > 0, "!>0"); //lock immediately, transfer directly to staker to skip an erc20 transfer - IERC20(crv).safeTransferFrom(msg.sender, staker, _amount); + IERC20(duck).safeTransferFrom(msg.sender, staker, _amount); _lockCurve(); //mint here @@ -99,7 +99,7 @@ contract CrvDepositor { } function depositAll(address _stakeAddress) external { - uint256 crvBal = IERC20(crv).balanceOf(msg.sender); - deposit(crvBal, _stakeAddress); + uint256 duckBal = IERC20(duck).balanceOf(msg.sender); + deposit(duckBal, _stakeAddress); } } \ No newline at end of file diff --git a/contracts/ExtraRewardStashV2.sol b/contracts/ExtraRewardStashV2.sol deleted file mode 100644 index 9ff5a7a..0000000 --- a/contracts/ExtraRewardStashV2.sol +++ /dev/null @@ -1,230 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.9; - -import "./Interfaces.sol"; -import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; -import '@openzeppelin/contracts/utils/Address.sol'; -import '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; -import '@openzeppelin/contracts/utils/math/SafeMath.sol'; - - -contract ExtraRewardStashV2 { - using SafeERC20 for IERC20; - using Address for address; - using SafeMath for uint256; - - address public constant crv = address(0xD533a949740bb3306d119CC777fa900bA034cd52); - uint256 private constant maxRewards = 8; - uint256 private constant WEEK = 7 * 86400; - - uint256 public pid; - address public operator; - address public staker; - address public gauge; - address public rewardFactory; - - mapping(address => uint256) public historicalRewards; - - struct TokenInfo { - address token; - address rewardAddress; - uint256 lastActiveTime; - } - uint256 public tokenCount; - TokenInfo[maxRewards] public tokenInfo; - - constructor() { - } - - function initialize(uint256 _pid, address _operator, address _staker, address _gauge, address _rFactory) external { - require(gauge == address(0),"2init"); - pid = _pid; - operator = _operator; - staker = _staker; - gauge = _gauge; - rewardFactory = _rFactory; - } - - function getName() external pure returns (string memory) { - return "ExtraRewardStashV2"; - } - - //try claiming if there are reward tokens registered - function claimRewards() external returns (bool) { - require(msg.sender == operator, "!authorized"); - - //this is updateable in v2 gauges now so must check each time. - checkForNewRewardTokens(); - - uint256 length = tokenCount; - if(length > 0){ - //get previous balances of all tokens - uint256[] memory balances = new uint256[](length); - for(uint256 i=0; i < length; i++){ - balances[i] = IERC20(tokenInfo[i].token).balanceOf(staker); - } - //claim rewards on gauge for staker - //booster will call for future proofing (cant assume anyone will always be able to call) - IDeposit(operator).claimRewards(pid,gauge); - - for(uint256 i=0; i < length; i++){ - address token = tokenInfo[i].token; - uint256 newbalance = IERC20(token).balanceOf(staker); - //stash if balance increased - if(newbalance > balances[i]){ - IStaker(staker).withdraw(token); - tokenInfo[i].lastActiveTime = block.timestamp; - - //make sure this pool is in active list, - IRewardFactory(rewardFactory).addActiveReward(token,pid); - - - //check if other stashes are also active, and if so, send to arbitrator - //do this here because processStash will have tokens from the arbitrator - uint256 activeCount = IRewardFactory(rewardFactory).activeRewardCount(token); - if(activeCount > 1){ - //send to arbitrator - address arb = IDeposit(operator).rewardArbitrator(); - if(arb != address(0)){ - IERC20(token).safeTransfer(arb, newbalance); - } - } - - }else{ - //check if this reward has been inactive too long - if(block.timestamp > tokenInfo[i].lastActiveTime + WEEK){ - //set as inactive - IRewardFactory(rewardFactory).removeActiveReward(token,pid); - }else{ - //edge case around reward ending periods - if(newbalance > 0){ - // - recently active pool - // - rewards claimed to staker contract via a deposit/withdraw(or someone manually calling on the gauge) - // - rewards ended before the deposit, thus deposit took the last available tokens - // - thus claimRewards doesnt see any new rewards, but there are rewards on the staker contract - // - i think its safe to assume claim will be called within the timeframe, or else these rewards - // will be unretrievable until some pool starts rewards again - - //claim the tokens - IStaker(staker).withdraw(token); - - uint256 activeCount = IRewardFactory(rewardFactory).activeRewardCount(token); - if(activeCount > 1){ - //send to arbitrator - address arb = IDeposit(operator).rewardArbitrator(); - if(arb != address(0)){ - IERC20(token).safeTransfer(arb, newbalance); - } - } - } - } - } - } - } - return true; - } - - - //check if gauge rewards have changed - function checkForNewRewardTokens() internal { - for(uint256 i = 0; i < maxRewards; i++){ - address token = ICurveGauge(gauge).reward_tokens(i); - if (token == address(0)) { - for (uint256 x = i; x < tokenCount; x++) { - IRewardFactory(rewardFactory).removeActiveReward(tokenInfo[x].token,pid); - } - if (i != tokenCount) { - tokenCount = i; - } - break; - } - setToken(i, token); - } - } - - //replace a token on token list - function setToken(uint256 _tid, address _token) internal { - TokenInfo storage t = tokenInfo[_tid]; - address currentToken = t.token; - if(currentToken != _token){ - //set old as inactive - IRewardFactory(rewardFactory).removeActiveReward(currentToken,pid); - - //set token address - t.token = _token; - - //create new reward contract - (,,,address mainRewardContract,,) = IDeposit(operator).poolInfo(pid); - address rewardContract = IRewardFactory(rewardFactory).CreateTokenRewards( - _token, - mainRewardContract, - address(this)); - t.rewardAddress = rewardContract; - t.lastActiveTime = 0; - //do not set as active yet, wait for first earmark - } - } - - //pull assigned tokens from staker to stash - function stashRewards() external returns(bool){ - require(msg.sender == operator, "!authorized"); - - //after depositing/withdrawing, extra incentive tokens are transfered to the staking contract - //need to pull them off and stash here. - for(uint i=0; i < tokenCount; i++){ - TokenInfo storage t = tokenInfo[i]; - address token = t.token; - if(token == address(0)) continue; - - //only stash if rewards are active - if(block.timestamp <= t.lastActiveTime + WEEK){ - uint256 before = IERC20(token).balanceOf(address(this)); - IStaker(staker).withdraw(token); - - - //check for multiple pools claiming same token - uint256 activeCount = IRewardFactory(rewardFactory).activeRewardCount(token); - if(activeCount > 1){ - //take difference of before/after(only send new tokens) - uint256 amount = IERC20(token).balanceOf(address(this)); - amount = amount.sub(before); - - //send to arbitrator - address arb = IDeposit(operator).rewardArbitrator(); - if(arb != address(0)){ - IERC20(token).safeTransfer(arb, amount); - } - } - } - } - return true; - } - - //send all extra rewards to their reward contracts - function processStash() external returns(bool){ - require(msg.sender == operator, "!operator"); - - for(uint i=0; i < tokenCount; i++){ - TokenInfo storage t = tokenInfo[i]; - address token = t.token; - if(token == address(0)) continue; - - uint256 amount = IERC20(token).balanceOf(address(this)); - if (amount > 0) { - historicalRewards[token] = historicalRewards[token].add(amount); - if(token == crv){ - //if crv, send back to booster to distribute - IERC20(token).safeTransfer(operator, amount); - continue; - } - //add to reward contract - address rewards = t.rewardAddress; - if(rewards == address(0)) continue; - IERC20(token).safeTransfer(rewards, amount); - IRewards(rewards).queueNewRewards(amount); - } - } - return true; - } - -} \ No newline at end of file diff --git a/contracts/ExtraRewardStashV3.sol_ b/contracts/ExtraRewardStashV3.sol_ deleted file mode 100644 index bf4cde9..0000000 --- a/contracts/ExtraRewardStashV3.sol_ +++ /dev/null @@ -1,892 +0,0 @@ -// contract deployed at 0xd7AbC64CAFc30FDd08A42Ea4bC13846be455399C and stored here as is -// used in StashFactoryV2 as v3 implementation - -// File: contracts\Interfaces.sol - -// SPDX-License-Identifier: MIT -pragma solidity 0.6.12; - - - -interface ICurveGauge { - function deposit(uint256) external; - function balanceOf(address) external view returns (uint256); - function withdraw(uint256) external; - function claim_rewards() external; - function reward_tokens(uint256) external view returns(address);//v2 - function rewarded_token() external view returns(address);//v1 - function lp_token() external view returns(address); -} - -interface ICurveVoteEscrow { - function create_lock(uint256, uint256) external; - function increase_amount(uint256) external; - function increase_unlock_time(uint256) external; - function withdraw() external; - function smart_wallet_checker() external view returns (address); -} - -interface IWalletChecker { - function check(address) external view returns (bool); -} - -interface IVoting{ - function vote(uint256, bool, bool) external; //voteId, support, executeIfDecided - function getVote(uint256) external view returns(bool,bool,uint64,uint64,uint64,uint64,uint256,uint256,uint256,bytes memory); - function vote_for_gauge_weights(address,uint256) external; -} - -interface IMinter{ - function mint(address) external; -} - -interface IRegistry{ - function get_registry() external view returns(address); - function get_address(uint256 _id) external view returns(address); - function gauge_controller() external view returns(address); - function get_lp_token(address) external view returns(address); - function get_gauges(address) external view returns(address[10] memory,uint128[10] memory); -} - -interface IStaker{ - function deposit(address, address) external; - function withdraw(address) external; - function withdraw(address, address, uint256) external; - function withdrawAll(address, address) external; - function createLock(uint256, uint256) external; - function increaseAmount(uint256) external; - function increaseTime(uint256) external; - function release() external; - function claimCrv(address) external returns (uint256); - function claimRewards(address) external; - function claimFees(address,address) external; - function setStashAccess(address, bool) external; - function vote(uint256,address,bool) external; - function voteGaugeWeight(address,uint256) external; - function balanceOfPool(address) external view returns (uint256); - function operator() external view returns (address); - function execute(address _to, uint256 _value, bytes calldata _data) external returns (bool, bytes memory); -} - -interface IRewards{ - function stake(address, uint256) external; - function stakeFor(address, uint256) external; - function withdraw(address, uint256) external; - function exit(address) external; - function getReward(address) external; - function queueNewRewards(uint256) external; - function notifyRewardAmount(uint256) external; - function addExtraReward(address) external; - function stakingToken() external view returns (address); - function rewardToken() external view returns(address); - function earned(address account) external view returns (uint256); -} - -interface IStash{ - function stashRewards() external returns (bool); - function processStash() external returns (bool); - function claimRewards() external returns (bool); - function initialize(uint256 _pid, address _operator, address _staker, address _gauge, address _rewardFactory) external; -} - -interface IFeeDistro{ - function claim() external; - function token() external view returns(address); -} - -interface ITokenMinter{ - function mint(address,uint256) external; - function burn(address,uint256) external; -} - -interface IDeposit{ - function isShutdown() external view returns(bool); - function balanceOf(address _account) external view returns(uint256); - function totalSupply() external view returns(uint256); - function poolInfo(uint256) external view returns(address,address,address,address,address, bool); - function rewardClaimed(uint256,address,uint256) external; - function withdrawTo(uint256,uint256,address) external; - function claimRewards(uint256,address) external returns(bool); - function rewardArbitrator() external returns(address); - function setGaugeRedirect(uint256 _pid) external returns(bool); - function owner() external returns(address); -} - -interface ICrvDeposit{ - function deposit(uint256, bool) external; - function lockIncentive() external view returns(uint256); -} - -interface IRewardFactory{ - function setAccess(address,bool) external; - function CreateCrvRewards(uint256,address) external returns(address); - function CreateTokenRewards(address,address,address) external returns(address); - function activeRewardCount(address) external view returns(uint256); - function addActiveReward(address,uint256) external returns(bool); - function removeActiveReward(address,uint256) external returns(bool); -} - -interface IStashFactory{ - function CreateStash(uint256,address,address,uint256) external returns(address); -} - -interface ITokenFactory{ - function CreateDepositToken(address) external returns(address); -} - -interface IPools{ - function addPool(address _lptoken, address _gauge, uint256 _stashVersion) external returns(bool); - function shutdownPool(uint256 _pid) external returns(bool); - function poolInfo(uint256) external view returns(address,address,address,address,address,bool); - function poolLength() external view returns (uint256); - function gaugeMap(address) external view returns(bool); - function setPoolManager(address _poolM) external; -} - -interface IVestedEscrow{ - function fund(address[] calldata _recipient, uint256[] calldata _amount) external returns(bool); -} - -// File: contracts\interfaces\IRewardHook.sol - -pragma solidity 0.6.12; - -interface IRewardHook { - function onRewardClaim() external; -} - -// File: @openzeppelin\contracts\math\SafeMath.sol - -pragma solidity >=0.6.0 <0.8.0; - -/** - * @dev Wrappers over Solidity's arithmetic operations with added overflow - * checks. - * - * Arithmetic operations in Solidity wrap on overflow. This can easily result - * in bugs, because programmers usually assume that an overflow raises an - * error, which is the standard behavior in high level programming languages. - * `SafeMath` restores this intuition by reverting the transaction when an - * operation overflows. - * - * Using this library instead of the unchecked operations eliminates an entire - * class of bugs, so it's recommended to use it always. - */ -library SafeMath { - /** - * @dev Returns the addition of two unsigned integers, with an overflow flag. - * - * _Available since v3.4._ - */ - function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { - uint256 c = a + b; - if (c < a) return (false, 0); - return (true, c); - } - - /** - * @dev Returns the substraction of two unsigned integers, with an overflow flag. - * - * _Available since v3.4._ - */ - function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { - if (b > a) return (false, 0); - return (true, a - b); - } - - /** - * @dev Returns the multiplication of two unsigned integers, with an overflow flag. - * - * _Available since v3.4._ - */ - function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { - // Gas optimization: this is cheaper than requiring 'a' not being zero, but the - // benefit is lost if 'b' is also tested. - // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 - if (a == 0) return (true, 0); - uint256 c = a * b; - if (c / a != b) return (false, 0); - return (true, c); - } - - /** - * @dev Returns the division of two unsigned integers, with a division by zero flag. - * - * _Available since v3.4._ - */ - function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { - if (b == 0) return (false, 0); - return (true, a / b); - } - - /** - * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. - * - * _Available since v3.4._ - */ - function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { - if (b == 0) return (false, 0); - return (true, a % b); - } - - /** - * @dev Returns the addition of two unsigned integers, reverting on - * overflow. - * - * Counterpart to Solidity's `+` operator. - * - * Requirements: - * - * - Addition cannot overflow. - */ - function add(uint256 a, uint256 b) internal pure returns (uint256) { - uint256 c = a + b; - require(c >= a, "SafeMath: addition overflow"); - return c; - } - - /** - * @dev Returns the subtraction of two unsigned integers, reverting on - * overflow (when the result is negative). - * - * Counterpart to Solidity's `-` operator. - * - * Requirements: - * - * - Subtraction cannot overflow. - */ - function sub(uint256 a, uint256 b) internal pure returns (uint256) { - require(b <= a, "SafeMath: subtraction overflow"); - return a - b; - } - - /** - * @dev Returns the multiplication of two unsigned integers, reverting on - * overflow. - * - * Counterpart to Solidity's `*` operator. - * - * Requirements: - * - * - Multiplication cannot overflow. - */ - function mul(uint256 a, uint256 b) internal pure returns (uint256) { - if (a == 0) return 0; - uint256 c = a * b; - require(c / a == b, "SafeMath: multiplication overflow"); - return c; - } - - /** - * @dev Returns the integer division of two unsigned integers, reverting on - * division by zero. The result is rounded towards zero. - * - * Counterpart to Solidity's `/` operator. Note: this function uses a - * `revert` opcode (which leaves remaining gas untouched) while Solidity - * uses an invalid opcode to revert (consuming all remaining gas). - * - * Requirements: - * - * - The divisor cannot be zero. - */ - function div(uint256 a, uint256 b) internal pure returns (uint256) { - require(b > 0, "SafeMath: division by zero"); - return a / b; - } - - /** - * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), - * reverting when dividing by zero. - * - * Counterpart to Solidity's `%` operator. This function uses a `revert` - * opcode (which leaves remaining gas untouched) while Solidity uses an - * invalid opcode to revert (consuming all remaining gas). - * - * Requirements: - * - * - The divisor cannot be zero. - */ - function mod(uint256 a, uint256 b) internal pure returns (uint256) { - require(b > 0, "SafeMath: modulo by zero"); - return a % b; - } - - /** - * @dev Returns the subtraction of two unsigned integers, reverting with custom message on - * overflow (when the result is negative). - * - * CAUTION: This function is deprecated because it requires allocating memory for the error - * message unnecessarily. For custom revert reasons use {trySub}. - * - * Counterpart to Solidity's `-` operator. - * - * Requirements: - * - * - Subtraction cannot overflow. - */ - function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { - require(b <= a, errorMessage); - return a - b; - } - - /** - * @dev Returns the integer division of two unsigned integers, reverting with custom message on - * division by zero. The result is rounded towards zero. - * - * CAUTION: This function is deprecated because it requires allocating memory for the error - * message unnecessarily. For custom revert reasons use {tryDiv}. - * - * Counterpart to Solidity's `/` operator. Note: this function uses a - * `revert` opcode (which leaves remaining gas untouched) while Solidity - * uses an invalid opcode to revert (consuming all remaining gas). - * - * Requirements: - * - * - The divisor cannot be zero. - */ - function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { - require(b > 0, errorMessage); - return a / b; - } - - /** - * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), - * reverting with custom message when dividing by zero. - * - * CAUTION: This function is deprecated because it requires allocating memory for the error - * message unnecessarily. For custom revert reasons use {tryMod}. - * - * Counterpart to Solidity's `%` operator. This function uses a `revert` - * opcode (which leaves remaining gas untouched) while Solidity uses an - * invalid opcode to revert (consuming all remaining gas). - * - * Requirements: - * - * - The divisor cannot be zero. - */ - function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { - require(b > 0, errorMessage); - return a % b; - } -} - -// File: @openzeppelin\contracts\token\ERC20\IERC20.sol - -pragma solidity >=0.6.0 <0.8.0; - -/** - * @dev Interface of the ERC20 standard as defined in the EIP. - */ -interface IERC20 { - /** - * @dev Returns the amount of tokens in existence. - */ - function totalSupply() external view returns (uint256); - - /** - * @dev Returns the amount of tokens owned by `account`. - */ - function balanceOf(address account) external view returns (uint256); - - /** - * @dev Moves `amount` tokens from the caller's account to `recipient`. - * - * Returns a boolean value indicating whether the operation succeeded. - * - * Emits a {Transfer} event. - */ - function transfer(address recipient, uint256 amount) external returns (bool); - - /** - * @dev Returns the remaining number of tokens that `spender` will be - * allowed to spend on behalf of `owner` through {transferFrom}. This is - * zero by default. - * - * This value changes when {approve} or {transferFrom} are called. - */ - function allowance(address owner, address spender) external view returns (uint256); - - /** - * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. - * - * Returns a boolean value indicating whether the operation succeeded. - * - * IMPORTANT: Beware that changing an allowance with this method brings the risk - * that someone may use both the old and the new allowance by unfortunate - * transaction ordering. One possible solution to mitigate this race - * condition is to first reduce the spender's allowance to 0 and set the - * desired value afterwards: - * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 - * - * Emits an {Approval} event. - */ - function approve(address spender, uint256 amount) external returns (bool); - - /** - * @dev Moves `amount` tokens from `sender` to `recipient` using the - * allowance mechanism. `amount` is then deducted from the caller's - * allowance. - * - * Returns a boolean value indicating whether the operation succeeded. - * - * Emits a {Transfer} event. - */ - function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); - - /** - * @dev Emitted when `value` tokens are moved from one account (`from`) to - * another (`to`). - * - * Note that `value` may be zero. - */ - event Transfer(address indexed from, address indexed to, uint256 value); - - /** - * @dev Emitted when the allowance of a `spender` for an `owner` is set by - * a call to {approve}. `value` is the new allowance. - */ - event Approval(address indexed owner, address indexed spender, uint256 value); -} - -// File: @openzeppelin\contracts\utils\Address.sol - -pragma solidity >=0.6.2 <0.8.0; - -/** - * @dev Collection of functions related to the address type - */ -library Address { - /** - * @dev Returns true if `account` is a contract. - * - * [IMPORTANT] - * ==== - * It is unsafe to assume that an address for which this function returns - * false is an externally-owned account (EOA) and not a contract. - * - * Among others, `isContract` will return false for the following - * types of addresses: - * - * - an externally-owned account - * - a contract in construction - * - an address where a contract will be created - * - an address where a contract lived, but was destroyed - * ==== - */ - function isContract(address account) internal view returns (bool) { - // This method relies on extcodesize, which returns 0 for contracts in - // construction, since the code is only stored at the end of the - // constructor execution. - - uint256 size; - // solhint-disable-next-line no-inline-assembly - assembly { size := extcodesize(account) } - return size > 0; - } - - /** - * @dev Replacement for Solidity's `transfer`: sends `amount` wei to - * `recipient`, forwarding all available gas and reverting on errors. - * - * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost - * of certain opcodes, possibly making contracts go over the 2300 gas limit - * imposed by `transfer`, making them unable to receive funds via - * `transfer`. {sendValue} removes this limitation. - * - * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. - * - * IMPORTANT: because control is transferred to `recipient`, care must be - * taken to not create reentrancy vulnerabilities. Consider using - * {ReentrancyGuard} or the - * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. - */ - function sendValue(address payable recipient, uint256 amount) internal { - require(address(this).balance >= amount, "Address: insufficient balance"); - - // solhint-disable-next-line avoid-low-level-calls, avoid-call-value - (bool success, ) = recipient.call{ value: amount }(""); - require(success, "Address: unable to send value, recipient may have reverted"); - } - - /** - * @dev Performs a Solidity function call using a low level `call`. A - * plain`call` is an unsafe replacement for a function call: use this - * function instead. - * - * If `target` reverts with a revert reason, it is bubbled up by this - * function (like regular Solidity function calls). - * - * Returns the raw returned data. To convert to the expected return value, - * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. - * - * Requirements: - * - * - `target` must be a contract. - * - calling `target` with `data` must not revert. - * - * _Available since v3.1._ - */ - function functionCall(address target, bytes memory data) internal returns (bytes memory) { - return functionCall(target, data, "Address: low-level call failed"); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with - * `errorMessage` as a fallback revert reason when `target` reverts. - * - * _Available since v3.1._ - */ - function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { - return functionCallWithValue(target, data, 0, errorMessage); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], - * but also transferring `value` wei to `target`. - * - * Requirements: - * - * - the calling contract must have an ETH balance of at least `value`. - * - the called Solidity function must be `payable`. - * - * _Available since v3.1._ - */ - function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { - return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); - } - - /** - * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but - * with `errorMessage` as a fallback revert reason when `target` reverts. - * - * _Available since v3.1._ - */ - function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) { - require(address(this).balance >= value, "Address: insufficient balance for call"); - require(isContract(target), "Address: call to non-contract"); - - // solhint-disable-next-line avoid-low-level-calls - (bool success, bytes memory returndata) = target.call{ value: value }(data); - return _verifyCallResult(success, returndata, errorMessage); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], - * but performing a static call. - * - * _Available since v3.3._ - */ - function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { - return functionStaticCall(target, data, "Address: low-level static call failed"); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], - * but performing a static call. - * - * _Available since v3.3._ - */ - function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) { - require(isContract(target), "Address: static call to non-contract"); - - // solhint-disable-next-line avoid-low-level-calls - (bool success, bytes memory returndata) = target.staticcall(data); - return _verifyCallResult(success, returndata, errorMessage); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], - * but performing a delegate call. - * - * _Available since v3.4._ - */ - function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { - return functionDelegateCall(target, data, "Address: low-level delegate call failed"); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], - * but performing a delegate call. - * - * _Available since v3.4._ - */ - function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { - require(isContract(target), "Address: delegate call to non-contract"); - - // solhint-disable-next-line avoid-low-level-calls - (bool success, bytes memory returndata) = target.delegatecall(data); - return _verifyCallResult(success, returndata, errorMessage); - } - - function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) { - if (success) { - return returndata; - } else { - // Look for revert reason and bubble it up if present - if (returndata.length > 0) { - // The easiest way to bubble the revert reason is using memory via assembly - - // solhint-disable-next-line no-inline-assembly - assembly { - let returndata_size := mload(returndata) - revert(add(32, returndata), returndata_size) - } - } else { - revert(errorMessage); - } - } - } -} - -// File: @openzeppelin\contracts\token\ERC20\SafeERC20.sol -pragma solidity >=0.6.0 <0.8.0; - - -/** - * @title SafeERC20 - * @dev Wrappers around ERC20 operations that throw on failure (when the token - * contract returns false). Tokens that return no value (and instead revert or - * throw on failure) are also supported, non-reverting calls are assumed to be - * successful. - * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, - * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. - */ -library SafeERC20 { - using SafeMath for uint256; - using Address for address; - - function safeTransfer(IERC20 token, address to, uint256 value) internal { - _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); - } - - function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { - _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); - } - - /** - * @dev Deprecated. This function has issues similar to the ones found in - * {IERC20-approve}, and its usage is discouraged. - * - * Whenever possible, use {safeIncreaseAllowance} and - * {safeDecreaseAllowance} instead. - */ - function safeApprove(IERC20 token, address spender, uint256 value) internal { - // safeApprove should only be called when setting an initial allowance, - // or when resetting it to zero. To increase and decrease it, use - // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' - // solhint-disable-next-line max-line-length - require((value == 0) || (token.allowance(address(this), spender) == 0), - "SafeERC20: approve from non-zero to non-zero allowance" - ); - _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); - } - - function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { - uint256 newAllowance = token.allowance(address(this), spender).add(value); - _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); - } - - function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal { - uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero"); - _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); - } - - /** - * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement - * on the return value: the return value is optional (but if data is returned, it must not be false). - * @param token The token targeted by the call. - * @param data The call data (encoded using abi.encode or one of its variants). - */ - function _callOptionalReturn(IERC20 token, bytes memory data) private { - // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since - // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that - // the target address contains contract code and also asserts for success in the low-level call. - - bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); - if (returndata.length > 0) { // Return data is optional - // solhint-disable-next-line max-line-length - require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); - } - } -} - -// File: contracts\ExtraRewardStashV3.sol - -pragma solidity 0.6.12; - - - -//Stash v3: support for curve gauge reward redirect -//v3.1: support for arbitrary token rewards outside of gauge rewards -// add reward hook to pull rewards during claims -//v3.2: move constuctor to init function for proxy creation - -contract ExtraRewardStashV3 { - using SafeERC20 for IERC20; - using Address for address; - using SafeMath for uint256; - - address public constant crv = address(0xD533a949740bb3306d119CC777fa900bA034cd52); - uint256 private constant maxRewards = 8; - - uint256 public pid; - address public operator; - address public staker; - address public gauge; - address public rewardFactory; - - mapping(address => uint256) public historicalRewards; - bool public hasRedirected; - bool public hasCurveRewards; - - struct TokenInfo { - address token; - address rewardAddress; - } - - //use mapping+array so that we dont have to loop check each time setToken is called - mapping(address => TokenInfo) public tokenInfo; - address[] public tokenList; - - //address to call for reward pulls - address public rewardHook; - - constructor() public { - } - - function initialize(uint256 _pid, address _operator, address _staker, address _gauge, address _rFactory) external { - require(gauge == address(0),"2init"); - pid = _pid; - operator = _operator; - staker = _staker; - gauge = _gauge; - rewardFactory = _rFactory; - } - - function getName() external pure returns (string memory) { - return "ExtraRewardStashV3.2"; - } - - function tokenCount() external view returns (uint256){ - return tokenList.length; - } - - //try claiming if there are reward tokens registered - function claimRewards() external returns (bool) { - require(msg.sender == operator, "!operator"); - - //this is updateable from v2 gauges now so must check each time. - checkForNewRewardTokens(); - - //make sure we're redirected - if(!hasRedirected){ - IDeposit(operator).setGaugeRedirect(pid); - hasRedirected = true; - } - - if(hasCurveRewards){ - //claim rewards on gauge for staker - //using reward_receiver so all rewards will be moved to this stash - IDeposit(operator).claimRewards(pid,gauge); - } - - //hook for reward pulls - if(rewardHook != address(0)){ - try IRewardHook(rewardHook).onRewardClaim(){ - }catch{} - } - return true; - } - - - //check if gauge rewards have changed - function checkForNewRewardTokens() internal { - for(uint256 i = 0; i < maxRewards; i++){ - address token = ICurveGauge(gauge).reward_tokens(i); - if (token == address(0)) { - break; - } - if(!hasCurveRewards){ - hasCurveRewards = true; - } - setToken(token); - } - } - - //register an extra reward token to be handled - // (any new incentive that is not directly on curve gauges) - function setExtraReward(address _token) external{ - //owner of booster can set extra rewards - require(IDeposit(operator).owner() == msg.sender, "!owner"); - setToken(_token); - } - - function setRewardHook(address _hook) external{ - //owner of booster can set reward hook - require(IDeposit(operator).owner() == msg.sender, "!owner"); - rewardHook = _hook; - } - - - //replace a token on token list - function setToken(address _token) internal { - TokenInfo storage t = tokenInfo[_token]; - - if(t.token == address(0)){ - //set token address - t.token = _token; - - //check if crv - if(_token != crv){ - //create new reward contract (for NON-crv tokens only) - (,,,address mainRewardContract,,) = IDeposit(operator).poolInfo(pid); - address rewardContract = IRewardFactory(rewardFactory).CreateTokenRewards( - _token, - mainRewardContract, - address(this)); - - t.rewardAddress = rewardContract; - } - //add token to list of known rewards - tokenList.push(_token); - } - } - - //pull assigned tokens from staker to stash - function stashRewards() external pure returns(bool){ - - //after depositing/withdrawing, extra incentive tokens are claimed - //but from v3 this is default to off, and this stash is the reward receiver too. - - return true; - } - - //send all extra rewards to their reward contracts - function processStash() external returns(bool){ - require(msg.sender == operator, "!operator"); - - uint256 tCount = tokenList.length; - for(uint i=0; i < tCount; i++){ - TokenInfo storage t = tokenInfo[tokenList[i]]; - address token = t.token; - if(token == address(0)) continue; - - uint256 amount = IERC20(token).balanceOf(address(this)); - if (amount > 0) { - historicalRewards[token] = historicalRewards[token].add(amount); - if(token == crv){ - //if crv, send back to booster to distribute - IERC20(token).safeTransfer(operator, amount); - continue; - } - //add to reward contract - address rewards = t.rewardAddress; - if(rewards == address(0)) continue; - IERC20(token).safeTransfer(rewards, amount); - IRewards(rewards).queueNewRewards(amount); - } - } - return true; - } - -} \ No newline at end of file diff --git a/contracts/FeePool.sol b/contracts/FeePool.sol index 09efb93..a0dcdad 100644 --- a/contracts/FeePool.sol +++ b/contracts/FeePool.sol @@ -51,7 +51,7 @@ contract TokenWrapper is ERC20 { IERC20 public immutable stakingToken; - constructor(address _stakingToken) ERC20 ("Staked uveCRV", "suveCRV") { + constructor(address _stakingToken) ERC20 ("Staked uveDUCK", "suveDUCK") { stakingToken = IERC20(_stakingToken); } diff --git a/contracts/Interfaces.sol b/contracts/Interfaces.sol index 34bef0e..dd1f1a2 100644 --- a/contracts/Interfaces.sol +++ b/contracts/Interfaces.sol @@ -1,67 +1,23 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.9; - - -interface ICurveGauge { - function deposit(uint256) external; - function balanceOf(address) external view returns (uint256); - function withdraw(uint256) external; - function claim_rewards() external; - function reward_tokens(uint256) external view returns(address);//v2 - function rewarded_token() external view returns(address);//v1 - function lp_token() external view returns(address); - - function add_reward(address _reward_token, address _distributor) external; // v3 - function deposit_reward_token(address _reward_token, uint256 _amount) external; // v3 -} - -interface ICurveVoteEscrow { +interface IVoteEscrow { function create_lock(uint256, uint256) external; function increase_amount(uint256) external; function increase_unlock_time(uint256) external; function withdraw() external; function smart_wallet_checker() external view returns (address); -} - -interface IWalletChecker { - function check(address) external view returns (bool); -} - -interface IVoting { - function vote(uint256, bool, bool) external; //voteId, support, executeIfDecided - function getVote(uint256) external view returns(bool,bool,uint64,uint64,uint64,uint64,uint256,uint256,uint256,bytes memory); - function vote_for_gauge_weights(address,uint256) external; -} -interface IMinter { - function mint(address) external; -} - -interface IRegistry { - function get_registry() external view returns(address); - function get_address(uint256 _id) external view returns(address); - function gauge_controller() external view returns(address); - function get_lp_token(address) external view returns(address); - function get_gauges(address) external view returns(address[10] memory,uint128[10] memory); + function commit_smart_wallet_checker(address addr) external; + function apply_smart_wallet_checker() external; } interface IStaker { - function deposit(address, address) external; - function withdraw(address) external; - function withdraw(address, address, uint256) external; - function withdrawAll(address, address) external; function createLock(uint256, uint256) external; function increaseAmount(uint256) external; function increaseTime(uint256) external; function release() external; - function claimCrv(address) external returns (uint256); - function claimRewards(address) external; function claimFees(address,address) external; - function setStashAccess(address, bool) external; - function vote(uint256,address,bool) external; - function voteGaugeWeight(address,uint256) external; - function balanceOfPool(address) external view returns (uint256); function operator() external view returns (address); function execute(address _to, uint256 _value, bytes calldata _data) external returns (bool, bytes memory); } @@ -80,12 +36,6 @@ interface IRewards { function earned(address account) external view returns (uint256); } -interface IStash { - function stashRewards() external returns (bool); - function processStash() external returns (bool); - function claimRewards() external returns (bool); - function initialize(uint256 _pid, address _operator, address _staker, address _gauge, address _rewardFactory) external; -} interface IFeeDistro { function claim() external; @@ -99,68 +49,17 @@ interface ITokenMinter{ interface IDeposit { function isShutdown() external view returns(bool); - function balanceOf(address _account) external view returns(uint256); - function totalSupply() external view returns(uint256); - function poolInfo(uint256) external view returns(address,address,address,address,address, bool); - function rewardClaimed(uint256,address,uint256) external; - function withdrawTo(uint256,uint256,address) external; - function claimRewards(uint256,address) external returns(bool); - function rewardArbitrator() external returns(address); - function setGaugeRedirect(uint256 _pid) external returns(bool); function owner() external returns(address); } -interface ICrvDeposit { - function deposit(uint256, bool) external; - function lockIncentive() external view returns(uint256); -} - -interface IRewardFactory { - function setAccess(address,bool) external; - function CreateCrvRewards(uint256,address) external returns(address); - function CreateTokenRewards(address,address,address) external returns(address); - function CreateFeePool(address,address) external returns(address); - function activeRewardCount(address) external view returns(uint256); - function addActiveReward(address,uint256) external returns(bool); - function removeActiveReward(address,uint256) external returns(bool); -} - -interface IStashFactory { - function CreateStash(uint256,address,address,uint256) external returns(address); -} - -interface ITokenFactory { - function CreateDepositToken(address) external returns(address); -} - -interface IPools { - function addPool(address _lptoken, address _gauge, uint256 _stashVersion) external returns(bool); - function setLiquidityLimit(uint256 _pid, uint256 _limit) external; - function shutdownPool(uint256 _pid) external returns(bool); - function poolInfo(uint256) external view returns(address,address,address,address,address,bool); - function poolLength() external view returns (uint256); - function gaugeMap(address) external view returns(bool); - function setPoolManager(address _poolM) external; -} - interface IVestedEscrow { function fund(address[] calldata _recipient, uint256[] calldata _amount) external returns(bool); } -interface IProxyFactory { - function clone(address _target) external returns(address); -} - -interface ISmartWalletWhitelist { +interface ISmartWalletAllowList { function approveWallet(address _wallet) external; } -interface IGaugeController { - function get_gauge_weight(address _gauge) external view returns(uint256); - function gauge_relative_weight(address _gauge) external view returns(uint256); - function vote_user_slopes(address,address) external view returns(uint256,uint256,uint256);//slope,power,end -} - interface IVault { function collaterals(address asset, address owner) external view returns(uint256); } diff --git a/contracts/RewardFactory.sol b/contracts/RewardFactory.sol deleted file mode 100644 index ce98cce..0000000 --- a/contracts/RewardFactory.sol +++ /dev/null @@ -1,106 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.9; - -import "./Interfaces.sol"; -import "./BaseRewardPool.sol"; -import "./VirtualBalanceRewardPool.sol"; -import '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; -import "./FeePool.sol"; - - -contract RewardFactory { - using Address for address; - - address public constant crv = address(0xD533a949740bb3306d119CC777fa900bA034cd52); - address public immutable uvecrv; - - address public operator; // booster - mapping (address => bool) private rewardAccess; - mapping(address => uint256[]) public rewardActiveList; - - constructor(address _operator, address _uvecrv) { - operator = _operator; - uvecrv = _uvecrv; - } - - //Get active count function - function activeRewardCount(address _reward) external view returns(uint256) { - return rewardActiveList[_reward].length; - } - - function addActiveReward(address _reward, uint256 _pid) external returns(bool) { - require(rewardAccess[msg.sender] == true, "!auth"); - if (_reward == address(0)) { - return true; - } - - uint256[] storage activeList = rewardActiveList[_reward]; - uint256 pid = _pid+1; //offset by 1 so that we can use 0 as empty - - uint256 length = activeList.length; - for(uint256 i = 0; i < length; i++) { - if (activeList[i] == pid) return true; - } - activeList.push(pid); - return true; - } - - function removeActiveReward(address _reward, uint256 _pid) external returns(bool) { - require(rewardAccess[msg.sender] == true, "!auth"); - if (_reward == address(0)) { - return true; - } - - uint256[] storage activeList = rewardActiveList[_reward]; - uint256 pid = _pid + 1; //offset by 1 so that we can use 0 as empty - - uint256 length = activeList.length; - for(uint256 i = 0; i < length; i++) { - if (activeList[i] == pid) { - if (i != length - 1) { - activeList[i] = activeList[length - 1]; - } - activeList.pop(); - break; - } - } - return true; - } - - //stash contracts need access to create new Virtual balance pools for extra gauge incentives(ex. snx) - function setAccess(address _stash, bool _status) external { - require(msg.sender == operator, "!auth"); - rewardAccess[_stash] = _status; - } - - //Create a Managed Reward Pool to handle distribution of all crv mined in a pool - function CreateCrvRewards(uint256 _pid, address _depositToken) external returns (address) { - require(msg.sender == operator, "!auth"); - - //operator = booster(deposit) contract so that new crv can be added and distributed - //reward manager = this factory so that extra incentive tokens(ex. snx) can be linked to the main managed reward pool - BaseRewardPool rewardPool = new BaseRewardPool(_pid, _depositToken, crv, operator, address(this)); - return address(rewardPool); - } - - //create a virtual balance reward pool that mimics the balance of a pool's main reward contract - //used for extra incentive tokens(ex. snx) as well as vecrv fees - function CreateTokenRewards(address _token, address _mainRewards, address _operator) external returns (address) { - require(msg.sender == operator || rewardAccess[msg.sender] == true, "!auth"); - - //create new pool, use main pool for balance lookup - VirtualBalanceRewardPool rewardPool = new VirtualBalanceRewardPool(_mainRewards, _token, _operator); - address rAddress = address(rewardPool); - //add the new pool to main pool's list of extra rewards, assuming this factory has "reward manager" role - IRewards(_mainRewards).addExtraReward(rAddress); - //return new pool's address - return rAddress; - } - - function CreateFeePool(address _token, address _operator) external returns (address) { - require(msg.sender == operator); - - FeePool rewardPool = new FeePool(uvecrv, _token, _operator); - return address(rewardPool); - } -} \ No newline at end of file diff --git a/contracts/SmartWalletAllowList.sol b/contracts/SmartWalletAllowList.sol new file mode 100644 index 0000000..0009bb3 --- /dev/null +++ b/contracts/SmartWalletAllowList.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +// Copy of SmartWalletWhitelist currently used for veCRV +// https://etherscan.io/address/0xca719728ef172d0961768581fdf35cb116e0b7a4 +pragma solidity ^0.8.0; + +interface SmartWalletChecker { + function check(address) external view returns (bool); +} + +contract SmartWalletAllowList { + + mapping(address => bool) public wallets; + address public dao; + address public checker; + address public future_checker; + + event ApproveWallet(address); + event RevokeWallet(address); + + constructor(address _dao) { + dao = _dao; + } + + function commitSetChecker(address _checker) external { + require(msg.sender == dao, "!dao"); + future_checker = _checker; + } + + function applySetChecker() external { + require(msg.sender == dao, "!dao"); + checker = future_checker; + } + + function approveWallet(address _wallet) public { + require(msg.sender == dao, "!dao"); + wallets[_wallet] = true; + + emit ApproveWallet(_wallet); + } + function revokeWallet(address _wallet) external { + require(msg.sender == dao, "!dao"); + wallets[_wallet] = false; + + emit RevokeWallet(_wallet); + } + + function check(address _wallet) external view returns (bool) { + bool _check = wallets[_wallet]; + if (_check) { + return _check; + } else { + if (checker != address(0)) { + return SmartWalletChecker(checker).check(_wallet); + } + } + return false; + } +} \ No newline at end of file diff --git a/contracts/StashFactoryV2.sol b/contracts/StashFactoryV2.sol deleted file mode 100644 index 5d843f9..0000000 --- a/contracts/StashFactoryV2.sol +++ /dev/null @@ -1,86 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.9; - -import "./Interfaces.sol"; -import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; -import '@openzeppelin/contracts/utils/Address.sol'; -import '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; - - -contract StashFactoryV2 { - using Address for address; - - bytes4 private constant rewarded_token = 0x16fa50b1; //rewarded_token() - bytes4 private constant reward_tokens = 0x54c49fe9; //reward_tokens(uint256) - bytes4 private constant rewards_receiver = 0x01ddabf1; //rewards_receiver(address) - address public constant proxyFactory = 0x66807B5598A848602734B82E432dD88DBE13fC8f; - - address public immutable operator; // booster - address public immutable rewardFactory; - - address public v1Implementation; - address public v2Implementation; - address public v3Implementation; - - constructor(address _operator, address _rewardFactory) { - operator = _operator; - rewardFactory = _rewardFactory; - } - - function setImplementation(address _v1, address _v2, address _v3) external { - require(msg.sender == IDeposit(operator).owner(), "!auth"); - - v1Implementation = _v1; - v2Implementation = _v2; - v3Implementation = _v3; - } - - //Create a stash contract for the given gauge. - //function calls are different depending on the version of curve gauges so determine which stash type is needed - function CreateStash(uint256 _pid, address _gauge, address _staker, uint256 _stashVersion) external returns(address) { - require(msg.sender == operator, "!authorized"); - - if (_stashVersion == uint256(3) && IsV3(_gauge)) { - //v3 - require(v3Implementation != address(0), "0 impl"); - address stash = IProxyFactory(proxyFactory).clone(v3Implementation); - IStash(stash).initialize(_pid, operator, _staker, _gauge, rewardFactory); - return stash; - } else if (_stashVersion == uint256(1) && IsV1(_gauge)) { - //v1 - require(v1Implementation != address(0), "0 impl"); - address stash = IProxyFactory(proxyFactory).clone(v1Implementation); - IStash(stash).initialize(_pid, operator, _staker, _gauge, rewardFactory); - return stash; - } else if (_stashVersion == uint256(2) && !IsV3(_gauge) && IsV2(_gauge)) { - //v2 - require(v2Implementation != address(0), "0 impl"); - address stash = IProxyFactory(proxyFactory).clone(v2Implementation); - IStash(stash).initialize(_pid, operator, _staker, _gauge, rewardFactory); - return stash; - } - bool isV1 = IsV1(_gauge); - bool isV2 = IsV2(_gauge); - bool isV3 = IsV3(_gauge); - require(!isV1 && !isV2 && !isV3, "stash version mismatch"); - return address(0); - } - - function IsV1(address _gauge) private returns(bool) { - bytes memory data = abi.encode(rewarded_token); - (bool success,) = _gauge.call(data); - return success; - } - - function IsV2(address _gauge) private returns(bool) { - bytes memory data = abi.encodeWithSelector(reward_tokens,uint256(0)); - (bool success,) = _gauge.call(data); - return success; - } - - function IsV3(address _gauge) private returns(bool) { - bytes memory data = abi.encodeWithSelector(rewards_receiver,address(0)); - (bool success,) = _gauge.call(data); - return success; - } -} \ No newline at end of file diff --git a/contracts/TokenFactory.sol b/contracts/TokenFactory.sol deleted file mode 100644 index f9ad899..0000000 --- a/contracts/TokenFactory.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.9; - -import "./DepositToken.sol"; - - -contract TokenFactory { - - address public operator; - - constructor(address _operator) { - operator = _operator; - } - - function CreateDepositToken(address _lptoken) external returns(address) { - require(msg.sender == operator, "!authorized"); - - DepositToken dtoken = new DepositToken(operator, _lptoken); - return address(dtoken); - } -} \ No newline at end of file diff --git a/contracts/UVECRV.sol b/contracts/UVEDUCK.sol similarity index 89% rename from contracts/UVECRV.sol rename to contracts/UVEDUCK.sol index eb6e86d..62b5f74 100644 --- a/contracts/UVECRV.sol +++ b/contracts/UVEDUCK.sol @@ -4,14 +4,14 @@ pragma solidity 0.8.9; import '@openzeppelin/contracts/token/ERC20/ERC20.sol'; -contract UVECRV is ERC20 { +contract UVEDUCK is ERC20 { address public operator; constructor() ERC20( - "Unit vote-escrowed CRV", - "uveCRV" + "Unit vote-escrowed DUCK", + "uveDUCK" ) { operator = msg.sender; @@ -22,7 +22,6 @@ contract UVECRV is ERC20 { operator = _operator; } - function mint(address _to, uint256 _amount) external { require(msg.sender == operator, "!authorized"); diff --git a/contracts/VirtualBalanceRewardPool.sol b/contracts/VirtualBalanceRewardPool.sol deleted file mode 100644 index 59fd2e4..0000000 --- a/contracts/VirtualBalanceRewardPool.sol +++ /dev/null @@ -1,215 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.9; -/** - *Submitted for verification at Etherscan.io on 2020-07-17 - */ - -/* - ____ __ __ __ _ - / __/__ __ ___ / /_ / / ___ / /_ (_)__ __ - _\ \ / // // _ \/ __// _ \/ -_)/ __// / \ \ / -/___/ \_, //_//_/\__//_//_/\__/ \__//_/ /_\_\ - /___/ -* Synthetix: VirtualBalanceRewardPool.sol -* -* Docs: https://docs.synthetix.io/ -* -* -* MIT License -* =========== -* -* Copyright (c) 2020 Synthetix -* -* Permission is hereby granted, free of charge, to any person obtaining a copy -* of this software and associated documentation files (the "Software"), to deal -* in the Software without restriction, including without limitation the rights -* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the Software is -* furnished to do so, subject to the following conditions: -* -* The above copyright notice and this permission notice shall be included in all -* copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -*/ - -import "./Interfaces.sol"; -import '@openzeppelin/contracts/utils/math/SafeMath.sol'; -import '@openzeppelin/contracts/utils/math/Math.sol'; -import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; -import '@openzeppelin/contracts/utils/Address.sol'; -import '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; - - -contract VirtualBalanceWrapper { - using SafeERC20 for IERC20; - - IDeposit public deposits; - - function totalSupply() public view returns (uint256) { - return deposits.totalSupply(); - } - - function balanceOf(address account) public view returns (uint256) { - return deposits.balanceOf(account); - } -} - -contract VirtualBalanceRewardPool is VirtualBalanceWrapper { - using SafeERC20 for IERC20; - using SafeMath for uint256; - - IERC20 public rewardToken; - uint256 public constant duration = 7 days; - - address public operator; - - uint256 public periodFinish = 0; - uint256 public rewardRate = 0; - uint256 public lastUpdateTime; - uint256 public rewardPerTokenStored; - uint256 public queuedRewards = 0; - uint256 public currentRewards = 0; - uint256 public historicalRewards = 0; - uint256 public newRewardRatio = 830; - mapping(address => uint256) public userRewardPerTokenPaid; - mapping(address => uint256) public rewards; - - event RewardAdded(uint256 reward); - event Staked(address indexed user, uint256 amount); - event Withdrawn(address indexed user, uint256 amount); - event RewardPaid(address indexed user, uint256 reward); - - constructor( - address deposit_, - address reward_, - address op_ - ) { - deposits = IDeposit(deposit_); - rewardToken = IERC20(reward_); - operator = op_; - } - - - modifier updateReward(address account) { - rewardPerTokenStored = rewardPerToken(); - lastUpdateTime = lastTimeRewardApplicable(); - if (account != address(0)) { - rewards[account] = earned(account); - userRewardPerTokenPaid[account] = rewardPerTokenStored; - } - _; - } - - function lastTimeRewardApplicable() public view returns (uint256) { - return Math.min(block.timestamp, periodFinish); - } - - function rewardPerToken() public view returns (uint256) { - if (totalSupply() == 0) { - return rewardPerTokenStored; - } - return - rewardPerTokenStored.add( - lastTimeRewardApplicable() - .sub(lastUpdateTime) - .mul(rewardRate) - .mul(1e18) - .div(totalSupply()) - ); - } - - function earned(address account) public view returns (uint256) { - return - balanceOf(account) - .mul(rewardPerToken().sub(userRewardPerTokenPaid[account])) - .div(1e18) - .add(rewards[account]); - } - - //update reward, emit, call linked reward's stake - function stake(address _account, uint256 amount) - external - updateReward(_account) - { - require(msg.sender == address(deposits), "!authorized"); - // require(amount > 0, 'VirtualDepositRewardPool: Cannot stake 0'); - emit Staked(_account, amount); - } - - function withdraw(address _account, uint256 amount) - public - updateReward(_account) - { - require(msg.sender == address(deposits), "!authorized"); - //require(amount > 0, 'VirtualDepositRewardPool : Cannot withdraw 0'); - - emit Withdrawn(_account, amount); - } - - function getReward(address _account) public updateReward(_account) { - uint256 reward = earned(_account); - if (reward > 0) { - rewards[_account] = 0; - rewardToken.safeTransfer(_account, reward); - emit RewardPaid(_account, reward); - } - } - - function getReward() external { - getReward(msg.sender); - } - - function donate(uint256 _amount) external returns(bool) { - IERC20(rewardToken).safeTransferFrom(msg.sender, address(this), _amount); - queuedRewards = queuedRewards.add(_amount); - } - - function queueNewRewards(uint256 _rewards) external { - require(msg.sender == operator, "!authorized"); - - _rewards = _rewards.add(queuedRewards); - - if (block.timestamp >= periodFinish) { - notifyRewardAmount(_rewards); - queuedRewards = 0; - return; - } - - //et = now - (finish-duration) - uint256 elapsedTime = block.timestamp.sub(periodFinish.sub(duration)); - //current at now: rewardRate * elapsedTime - uint256 currentAtNow = rewardRate * elapsedTime; - uint256 queuedRatio = currentAtNow.mul(1000).div(_rewards); - if (queuedRatio < newRewardRatio) { - notifyRewardAmount(_rewards); - queuedRewards = 0; - } else { - queuedRewards = _rewards; - } - } - - function notifyRewardAmount(uint256 reward) - internal - updateReward(address(0)) - { - historicalRewards = historicalRewards.add(reward); - if (block.timestamp >= periodFinish) { - rewardRate = reward.div(duration); - } else { - uint256 remaining = periodFinish.sub(block.timestamp); - uint256 leftover = remaining.mul(rewardRate); - reward = reward.add(leftover); - rewardRate = reward.div(duration); - } - currentRewards = reward; - lastUpdateTime = block.timestamp; - periodFinish = block.timestamp.add(duration); - emit RewardAdded(reward); - } -} \ No newline at end of file diff --git a/contracts/VoterProxy.sol b/contracts/VoterProxy.sol new file mode 100644 index 0000000..b7314f0 --- /dev/null +++ b/contracts/VoterProxy.sol @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.9; + +import "./Interfaces.sol"; +import "./Addresses.sol"; +import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; +import '@openzeppelin/contracts/utils/Address.sol'; +import '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; +import '@openzeppelin/contracts/utils/math/SafeMath.sol'; + + +contract VoterProxy is Addresses { + using SafeMath for uint; + using SafeERC20 for IERC20; + using Address for address; + + address public constant escrow = veDuck; + + address public owner; + address public operator; // Booster + address public depositor; + + constructor() { + owner = msg.sender; + } + + function setOwner(address _owner) external { + require(msg.sender == owner, "!auth"); + owner = _owner; + } + + function setOperator(address _operator) external { + require(msg.sender == owner, "!auth"); + require(operator == address(0) || IDeposit(operator).isShutdown() == true, "needs shutdown"); + + operator = _operator; + } + + function setDepositor(address _depositor) external { + require(msg.sender == owner, "!auth"); + + depositor = _depositor; + } + + function createLock(uint256 _value, uint256 _unlockTime) external returns(bool) { + require(msg.sender == depositor, "!auth"); + IERC20(duck).safeApprove(escrow, 0); + IERC20(duck).safeApprove(escrow, _value); + IVoteEscrow(escrow).create_lock(_value, _unlockTime); + return true; + } + + function increaseAmount(uint256 _value) external returns(bool) { + require(msg.sender == depositor, "!auth"); + IERC20(duck).safeApprove(escrow, 0); + IERC20(duck).safeApprove(escrow, _value); + IVoteEscrow(escrow).increase_amount(_value); + return true; + } + + function increaseTime(uint256 _value) external returns(bool) { + require(msg.sender == depositor, "!auth"); + IVoteEscrow(escrow).increase_unlock_time(_value); + return true; + } + + function release() external returns(bool) { + require(msg.sender == depositor, "!auth"); + IVoteEscrow(escrow).withdraw(); + return true; + } + + function claimFees(address _distroContract, address _token) external returns (uint256) { + require(msg.sender == operator, "!auth"); + IFeeDistro(_distroContract).claim(); + uint256 _balance = IERC20(_token).balanceOf(address(this)); + IERC20(_token).safeTransfer(operator, _balance); + return _balance; + } + + function execute( + address _to, + uint256 _value, + bytes calldata _data + ) external returns (bool, bytes memory) { + require(msg.sender == operator, "!auth"); + + (bool success, bytes memory result) = _to.call{value:_value}(_data); + + return (success, result); + } + + function read(address _to, bytes calldata _callData) public view returns (bool success, bytes memory data) { + (success, data) = _to.staticcall(_callData); + } +} \ No newline at end of file diff --git a/contracts/helpers/UnitProtocol.sol b/contracts/helpers/UnitProtocol.sol index 3a1962f..20d5e54 100644 --- a/contracts/helpers/UnitProtocol.sol +++ b/contracts/helpers/UnitProtocol.sol @@ -27,4 +27,10 @@ interface ICDPManager01 { interface IWrappedToUnderlyingOracle { function setUnderlying(address wrapped, address underlying) external; + function assetToUsd(address asset, uint amount) external view returns (uint); +} + +interface IVeDistribution { + function checkpoint_token() external; + function checkpoint_total_supply() external; } \ No newline at end of file diff --git a/scripts/deployFns.js b/scripts/deployFns.js index 820f1c9..6e628a0 100644 --- a/scripts/deployFns.js +++ b/scripts/deployFns.js @@ -1,45 +1,48 @@ const {ethers} = require("hardhat"); -const extraRewardStashV3 = '0xd7AbC64CAFc30FDd08A42Ea4bC13846be455399C' -const usdpLPAddress = '0x7eb40e450b9655f4b3cc4259bcc731c63ff55ae6' -const usdpGauge = '0x055be5DDB7A925BfEF3417FC157f53CA77cA7222' +const veDistribution = '0x9f2138ccb930f0654B2C40E7e29FF8291452Eed8'; +const multisig = '0xae37E8f9a3f960eE090706Fa4db41Ca2f2C56Cb8'; async function deploy() { - const voteProxy = await (await ethers.getContractFactory("CurveVoterProxy")).deploy() + const voteProxy = await (await ethers.getContractFactory("VoterProxy")).deploy() + await voteProxy.deployed(); + const uveDuck = await (await ethers.getContractFactory("UVEDUCK")).deploy() + await uveDuck.deployed(); const booster = await (await ethers.getContractFactory("Booster")).deploy(voteProxy.address) - const uveCrv = await (await ethers.getContractFactory("UVECRV")).deploy() - const rewardFactory = await (await ethers.getContractFactory("RewardFactory")).deploy(booster.address, uveCrv.address) - const stashFactory = await (await ethers.getContractFactory("StashFactoryV2")).deploy(booster.address, rewardFactory.address) - const extraRewardStashV2 = await (await ethers.getContractFactory("ExtraRewardStashV2")).deploy() - const tokenFactory = await (await ethers.getContractFactory("TokenFactory")).deploy(booster.address) - const crvDepositor = await (await ethers.getContractFactory("CrvDepositor")).deploy(voteProxy.address, uveCrv.address) + await booster.deployed(); + const duckDepositor = await (await ethers.getContractFactory("DuckDepositor")).deploy(voteProxy.address, uveDuck.address) + await duckDepositor.deployed(); + const allowList = await (await ethers.getContractFactory("SmartWalletAllowList")).deploy(multisig) + await allowList.deployed(); // initial settings - await stashFactory.setImplementation(ethers.constants.AddressZero, extraRewardStashV2.address, extraRewardStashV3) - await uveCrv.setOperator(crvDepositor.address) - await booster.setFactories(rewardFactory.address, stashFactory.address, tokenFactory.address) - await booster.setFeeInfo() + await uveDuck.setOperator(duckDepositor.address) - await voteProxy.setOperator(booster.address) - await voteProxy.setDepositor(crvDepositor.address) + await booster.setFeeInfo(veDistribution, uveDuck.address); - await booster.addPool(usdpLPAddress, usdpGauge, 2) // initial pool for usdplp + await voteProxy.setOperator(booster.address) + await voteProxy.setDepositor(duckDepositor.address) // manual config - // await crv.connect(m).transfer(voteProxy.address, 1) // transfers on voteproxy some crv for initial lock - // await crvDepositor.initialLock() - // await booster.voteGaugeWeight([usdpGauge], [10000]) + // await duck.connect(m).transfer(voteProxy.address, 1) // transfers on voteproxy some duck for initial lock + // await duckDepositor.initialLock() + + // await veDuck.connect(unitMultisig).commit_smart_wallet_checker(allowList.address); + // await veDuck.connect(unitMultisig).apply_smart_wallet_checker(); + // await allowList.connect(unitMultisig).approveWallet(voteProxy.address); + + // booster - owner and feemanager to multisig + // VoterProxy - owner to multisig + // Duck depositor - feeManager to multisig + // SmartWalletAllowList - dao to multisig const contracts = { voteProxy, booster, - uveCrv, - rewardFactory, - stashFactory, - extraRewardStashV2, - tokenFactory, - crvDepositor + uveDuck, + duckDepositor, + allowList }; diff --git a/test/test.js b/test/test.js index 193d951..32064fa 100644 --- a/test/test.js +++ b/test/test.js @@ -1,340 +1,162 @@ const chai = require("chai") -const { solidity } = require("ethereum-waffle") +const {solidity} = require("ethereum-waffle") chai.use(solidity) -const { ethers } = require('hardhat') +const {ethers} = require('hardhat') const {deploy} = require("../scripts/deployFns"); const {expect} = require("chai"); const DAY = 86400 const WEEK = 7 * DAY -const USDP_POOL_ID = 0; -const CVXCRV_POOL_ID = 1; - -const crvWhaleAddress = '0x7a16fF8270133F063aAb6C9977183D9e72835428' -const usdpLPWhaleAddress = '0x1b5eb1173d2bf770e50f10410c9a96f7a8eb6e75' -const usdpAddress = '0x1456688345527be1f37e9e627da0837d6f08c925' -const wcd = '0x40907540d8a6c65c637785e8f8b742ae6b0b9968' -const sww = '0xca719728Ef172d0961768581fdF35CB116e0B7a4' -const usdpLPAddress = '0x7eb40e450b9655f4b3cc4259bcc731c63ff55ae6' -const usdpGauge = '0x055be5DDB7A925BfEF3417FC157f53CA77cA7222' -const extraRewardStashV3 = '0xd7AbC64CAFc30FDd08A42Ea4bC13846be455399C' -const unitVault = '0xb1cFF81b9305166ff1EFc49A129ad2AfCd7BCf19' - -// v3 gauge -const cvxcrvGaugeAddress = '0x903da6213a5a12b61c821598154efad98c3b20e4'; -const cvxcrvLPAddress = '0x9d0464996170c6b9e75eed71c68b99ddedf279e8'; -const cvxcrvLPWhaleAddress = '0x903da6213a5a12b61c821598154efad98c3b20e4'; -const cvxAddress = '0x4e3fbd56cd56c3e72c1403e103b45db9da5b9d2b'; -const cvxWhaleAddress = '0x5f465e9fcffc217c5849906216581a657cd60605'; -const cvxcrvGaugeAdminAddress = '0x2ef1bc1961d3209e5743c91cd3fbfa0d08656bc3' - -let crv, admin, alice, bob, charlie, crvWhale, walletCheckerDao, - smartWalletWhiteList, usdpLPWhale, usdpLP, threecrv, dai, usdp, - cvxcrvLP, cvxcrvLPWhale, cvx, cvxWhale, cvxcrvGaugeAdmin, cvxcrvGauge - -let voteProxy, booster, uveCrv, rewardFactory, - stashFactory, extraRewardStashV2, tokenFactory, - crvDepositor, lockFees, unitMultisig +const duckWhaleAddress = '0x3976cdc41f34466ebb7efa2fd097d3eab808ea65' -let oracleRegistry, cdpManager, wrappedToUnderlyingOracle, unitParameters - -const crvDepositAmount = 10n ** 6n * 10n ** 18n - -describe("Functional test", function() { - - before(async function () { - crv = await ethers.getContractAt("IERC20", "0xD533a949740bb3306d119CC777fa900bA034cd52") - dai = await ethers.getContractAt("IERC20", "0x6B175474E89094C44Da98b954EedeAC495271d0F") - threecrv = await ethers.getContractAt("IERC20", "0x6c3f90f043a72fa612cbac8115ee7e52bde6e490") - usdpLP = await ethers.getContractAt("IERC20", usdpLPAddress) - usdp = await ethers.getContractAt("IERC20", usdpAddress) - smartWalletWhiteList = await ethers.getContractAt("ISmartWalletWhitelist", sww) - ;[admin, alice, bob, charlie] = await ethers.getSigners() - - oracleRegistry = await ethers.getContractAt("IOracleRegistry", '0x75fBFe26B21fd3EA008af0C764949f8214150C8f') - cdpManager = await ethers.getContractAt("ICDPManager01", '0xee84F58Ee39C6122C76C1Fa54f0B6f33da1642Ec') - wrappedToUnderlyingOracle = await ethers.getContractAt("IWrappedToUnderlyingOracle", '0x220Ea780a484c18fd0Ab252014c58299759a1Fbd') - unitParameters = await ethers.getContractAt("IParametersBatchUpdater", '0x4DD1A6DB148BEcDADAdFC407D23b725eDd3cfB6f') - - await ethers.provider.send("hardhat_impersonateAccount", [crvWhaleAddress]); - await ethers.provider.send("hardhat_impersonateAccount", [usdpLPWhaleAddress]); - crvWhale = await ethers.getSigner(crvWhaleAddress) - usdpLPWhale = await ethers.getSigner(usdpLPWhaleAddress) - await ethers.provider.send("hardhat_setBalance", [usdpLPWhaleAddress, '0x3635c9adc5dea00000' /* 1000Ether */]); - - await ethers.provider.send("hardhat_impersonateAccount", ['0xae37E8f9a3f960eE090706Fa4db41Ca2f2C56Cb8']); - unitMultisig = await ethers.getSigner("0xae37E8f9a3f960eE090706Fa4db41Ca2f2C56Cb8") - await ethers.provider.send("hardhat_setBalance", ["0xae37E8f9a3f960eE090706Fa4db41Ca2f2C56Cb8", '0x3635c9adc5dea00000' /* 1000Ether */]); - - await ethers.provider.send("hardhat_impersonateAccount", [wcd]); - walletCheckerDao = await ethers.getSigner(wcd); - - ( - { - voteProxy, - booster, - uveCrv, - rewardFactory, - stashFactory, - extraRewardStashV2, - tokenFactory, - crvDepositor - } = await deploy() - ); - - lockFees = await ethers.getContractAt("FeePool", await booster.lockFees()) - - // cvxCRV for gaugev3 - cvxcrvLP = await ethers.getContractAt("IERC20", cvxcrvLPAddress) - await ethers.provider.send("hardhat_impersonateAccount", [cvxcrvLPWhaleAddress]); - cvxcrvLPWhale = await ethers.getSigner(cvxcrvLPWhaleAddress) - await ethers.provider.send("hardhat_setBalance", [cvxcrvLPWhaleAddress, '0x3635c9adc5dea00000' /* 1000Ether */]); - - cvx = await ethers.getContractAt("IERC20", cvxAddress) - await ethers.provider.send("hardhat_impersonateAccount", [cvxWhaleAddress]); - cvxWhale = await ethers.getSigner(cvxWhaleAddress) - await ethers.provider.send("hardhat_setBalance", [cvxWhaleAddress, '0x3635c9adc5dea00000' /* 1000Ether */]); - - await ethers.provider.send("hardhat_impersonateAccount", [cvxcrvGaugeAdminAddress]); - cvxcrvGaugeAdmin = await ethers.getSigner(cvxcrvGaugeAdminAddress) - await ethers.provider.send("hardhat_setBalance", [cvxcrvGaugeAdminAddress, '0x3635c9adc5dea00000' /* 1000Ether */]); - - cvxcrvGauge = await ethers.getContractAt("ICurveGauge", cvxcrvGaugeAddress) - - await booster.addPool(cvxcrvLPAddress, cvxcrvGaugeAddress, 3) // pool for cvxCRV - }) - - it("CRV -> UVECRV", async function() { - - await crv.connect(crvWhale).transfer(voteProxy.address, 1) // transfers on voteproxy some crv for initial lock - - await smartWalletWhiteList.connect(walletCheckerDao).approveWallet(voteProxy.address) - - await crvDepositor.initialLock() - await crv.connect(crvWhale).approve(crvDepositor.address, crvDepositAmount) - - const depositResult1 = await crvDepositor.connect(crvWhale)['deposit(uint256,address)'](crvDepositAmount, lockFees.address) - console.log('Deposit gas used', (await depositResult1.wait()).gasUsed.toString()) - }) - - it("voting", async function() { - const gaugeController = await ethers.getContractAt('IGaugeController', '0x2F50D538606Fa9EDD2B11E2446BEb18C9D5846bB') - console.log('weight before', BigInt(await gaugeController.get_gauge_weight(usdpGauge))) - await booster.voteGaugeWeight([usdpGauge], [10000]) - console.log('weight after', BigInt(await gaugeController.get_gauge_weight(usdpGauge))) - }) - - it("3crv claim for UVECRV provided as collateral on Unit", async function() { - - // add SUVECRV as collateral on Unit Protocol (unit side) - await oracleRegistry.connect(unitMultisig).setOracleTypeForAsset(lockFees.address, 11) - await wrappedToUnderlyingOracle.connect(unitMultisig).setUnderlying(lockFees.address, crv.address) - await unitParameters.connect(unitMultisig).setCollaterals([lockFees.address], 0, 0, 50, 75, 0, 1100, 10n ** 23n, [11]) - - // provide collateral and mint some USDP - await lockFees.connect(crvWhale).approve(unitVault, crvDepositAmount) - await usdp.connect(crvWhale).approve(cdpManager.address, 10n ** 20n) - await cdpManager.connect(crvWhale).join(lockFees.address, crvDepositAmount, 10n ** 21n) - - // await booster.setRewardContract(ethers.constants.AddressZero /*veDistribution*/); - - const burner = await ethers.getContractAt("IBurner", '0xeCb456EA5365865EbAb8a2661B0c503410e9B347') - const underlyingBurner = await ethers.getContractAt("IBurner", '0x786B374B5eef874279f4B7b4de16940e57301A58') - - // 3crv claim - - await booster.earmarkFees(); - console.log("fees earmarked") - - await threecrv.balanceOf(lockFees.address).then( a => console.log("lockFees balance: ", BigInt(a))) - - await ethers.provider.send('evm_mine', [await chainTime() + WEEK + WEEK + DAY]) - console.log("advance time...") - - - /// ----- burn fees to vecrv claim contracts (curve dao side) ---- - const burnerBalance = BigInt(await threecrv.balanceOf("0xA464e6DCda8AC41e03616F95f4BC98a13b8922Dc")); - console.log("3crv on burner: ", burnerBalance) - - const daiBalance = BigInt(await dai.balanceOf(burner.address)) - console.log("burner dai:", daiBalance) - - await burner.withdraw_admin_fees("0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7") - console.log("admin fees withdrawn from pool") - - const daiBalance2 = BigInt(await dai.balanceOf(burner.address)) - console.log("burner dai:", daiBalance2) - - const underlyingDaiBalance = BigInt(await dai.balanceOf(underlyingBurner.address)) - console.log("underlyingBurner dai:", underlyingDaiBalance) +const unitVaultAddress = '0xb1cFF81b9305166ff1EFc49A129ad2AfCd7BCf19' +const unitMultisigAddress = '0xae37E8f9a3f960eE090706Fa4db41Ca2f2C56Cb8'; - await burner.burn(dai.address) - await burner.burn("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") - await burner.burn("0xdAC17F958D2ee523a2206206994597C13D831ec7") - console.log("burnt single coins") +const veDuckAddress = '0x48DdD27a4d54CD3e8c34F34F7e66e998442DBcE3' +const veDistributionAddress = '0x9f2138ccb930f0654B2C40E7e29FF8291452Eed8' +const duckAddress = '0x92E187a03B6CD19CB6AF293ba17F2745Fd2357D5'; +const usdpAddress = '0x1456688345527bE1f37E9e627DA0837D6f08C925'; +const wethAddress = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'; - await dai.balanceOf(burner.address).then(a => console.log("burner dai: ", BigInt(a))) - await dai.balanceOf(underlyingBurner.address).then(a => console.log("dai on underlyingburner: ", BigInt(a))) +const treasuryAddress = '0x0000000000000000000000000000000000000003'; - //execute to wrap everything to 3crv then send to "receiver" at 0xa464 - await underlyingBurner.execute() - console.log("burner executed") +let duck, admin, user1, user2, user3, duckWhale, + usdp, veDuck, veDistribution; - //should be zero now that its transfered - await dai.balanceOf(burner.address).then(a => console.log("burner dai: ", BigInt(a))) - await dai.balanceOf(underlyingBurner.address).then( a => console.log("dai on underlyingburner: ", BigInt(a))) +let voteProxy, booster, uveDuck, allowList, + duckDepositor, suveDuck, unitMultisig - //burn 3crv - await burner.burn(threecrv.address) - console.log("burn complete, checkpoint 3crv") - - const burnerBalance2 = await threecrv.balanceOf("0xA464e6DCda8AC41e03616F95f4BC98a13b8922Dc") - console.log("3crv on burner:", BigInt(burnerBalance2)) - /// ----- end of burn fees to vecrv claim contracts (curve dao side) ---- - - console.log('currentRewards', BigInt(await lockFees.currentRewards())) - await booster.earmarkFees() - console.log('currentRewards', BigInt(await lockFees.currentRewards())) - - await threecrv.balanceOf(lockFees.address).then( a => console.log("lockFees balance: ", BigInt(a))) - - await lockFees.earned(crvWhaleAddress).then(a => console.log("earned fees: ", BigInt(a))) - - console.log("advance time..."); - await ethers.provider.send('evm_mine', [await chainTime() + DAY]) - await lockFees.earned(crvWhaleAddress).then(a => console.log("earned fees: ", BigInt(a))) - - console.log("advance time..."); - await ethers.provider.send('evm_mine', [await chainTime() + DAY]) - await lockFees.earned(crvWhaleAddress).then(a => console.log("earned fees: ", BigInt(a))) - - const balanceBefore = BigInt(await threecrv.balanceOf(crvWhaleAddress)) - await lockFees.connect(crvWhale)['getReward()']() - const balanceAfter = BigInt(await threecrv.balanceOf(crvWhaleAddress)) - console.log('reward claimed', balanceAfter - balanceBefore, '3CRV') - }) - - it("lp staking", async function() { - const depositAmount = 10n ** 21n - - // stake usdpCRV - await usdpLP.connect(usdpLPWhale).approve(booster.address, depositAmount) - await booster.connect(usdpLPWhale).deposit(USDP_POOL_ID, depositAmount, true) - console.log('usdpCRV deposited & staked') - }) - - it("CRV claim for lp staking", async function() { - const poolinfo = await booster.poolInfo(USDP_POOL_ID) - const rewardPoolAddress = poolinfo.crvRewards - const rewardPool = await ethers.getContractAt("BaseRewardPool", rewardPoolAddress) - const depositToken = await ethers.getContractAt("DepositToken", poolinfo.token) - - console.log("pool lp token " +poolinfo.lptoken) - console.log("pool gauge " +poolinfo.gauge) - console.log("pool reward contract at " +rewardPool.address) - - await depositToken.name().then(a=>console.log("deposit token name: " +a)) - await depositToken.symbol().then(a=>console.log("deposit token symbol: " +a)) - - await ethers.provider.send('evm_mine', [await chainTime() + DAY]) - - //collect and distribute rewards off gauge - await booster.earmarkRewards(USDP_POOL_ID) - console.log("----earmark 1----") - - await rewardPool.earned(usdpLPWhaleAddress).then(a => console.log("claimable", BigInt(a), 'CRV')) - - console.log("advance time...") - await ethers.provider.send('evm_mine', [await chainTime() + 6 * DAY]) - - await booster.earmarkRewards(USDP_POOL_ID) - console.log("----earmark 2----") - - await rewardPool.earned(usdpLPWhaleAddress).then(a => console.log("claimable", BigInt(a), 'CRV')) - - await crv.balanceOf(rewardPool.address).then(a=>console.log("crv at reward pool", BigInt(a))) - - console.log("advance time...") - await ethers.provider.send('evm_mine', [await chainTime() + 7 * DAY]) - await rewardPool.earned(usdpLPWhaleAddress).then(a => console.log("claimable", BigInt(a), 'CRV')) - await rewardPool.connect(usdpLPWhale)['getReward()']() - await crv.balanceOf(usdpLPWhaleAddress).then(a=>console.log("claimed", BigInt(a), 'CRV')) - }) - - - it("lp staking v3", async function() { - const depositAmount = 1000n * 10n ** 18n - - // stake cvxCRV - await cvxcrvLP.connect(cvxcrvLPWhale).approve(booster.address, depositAmount) - await booster.connect(cvxcrvLPWhale).deposit(CVXCRV_POOL_ID, depositAmount, true) - console.log('cvxcrv deposited & staked') - }) - - it("CRV+additional tokens claim for lp staking v3", async function() { - const poolinfo = await booster.poolInfo(CVXCRV_POOL_ID) - const rewardPoolAddress = poolinfo.crvRewards - const stashV3Address = poolinfo.stash - const rewardPool = await ethers.getContractAt("BaseRewardPool", rewardPoolAddress) - const depositToken = await ethers.getContractAt("DepositToken", poolinfo.token) - - expect(poolinfo.lptoken.toLowerCase()).to.be.equal(cvxcrvLPAddress) - expect(poolinfo.gauge.toLowerCase()).to.be.equal(cvxcrvGaugeAddress) - - console.log("pool lp token " +poolinfo.lptoken) - console.log("pool gauge " +poolinfo.gauge) - console.log("pool reward contract at " +rewardPool.address) - console.log("stash v3 contract at " + stashV3Address) - - let crvBalance = await crv.balanceOf(cvxcrvLPWhaleAddress) - let cvxBalance = await cvx.balanceOf(cvxcrvLPWhaleAddress) - expect(BigInt(cvxBalance)).to.be.equal(0n) - console.log('before all rewards manipulations: CRV:', BigInt(crvBalance), 'CVX:', BigInt(cvxBalance)) - - await depositToken.name().then(a=>console.log("deposit token name: " +a)) - await depositToken.symbol().then(a=>console.log("deposit token symbol: " +a)) - - // add reward in cvx token - expect(await rewardPool.extraRewardsLength()).to.be.equal(0); - await cvxcrvGauge.connect(cvxcrvGaugeAdmin).add_reward(cvxAddress, cvxWhaleAddress); - await cvx.connect(cvxWhale).approve(cvxcrvGauge.address, 1000n * 10n ** 18n) - await cvxcrvGauge.connect(cvxWhale).deposit_reward_token(cvxAddress, 1000n * 10n ** 18n) - expect(await rewardPool.extraRewardsLength()).to.be.equal(0); - - await ethers.provider.send('evm_mine', [await chainTime() + DAY]) - - //collect and distribute rewards off gauge - await booster.earmarkRewards(CVXCRV_POOL_ID) - // added additional token for rewards - expect(await rewardPool.extraRewardsLength()).to.be.equal(1); - const extraRewardPool = await ethers.getContractAt('VirtualBalanceRewardPool', await rewardPool.extraRewards(0)); - expect((await extraRewardPool.rewardToken()).toLowerCase()).to.be.equal(cvxAddress); - console.log("----earmark 1----") - - await rewardPool.earned(cvxcrvLPWhaleAddress).then(a => console.log("claimable", BigInt(a), 'CRV')) - - console.log("advance time...") - await ethers.provider.send('evm_mine', [await chainTime() + 6 * DAY]) - - await booster.earmarkRewards(CVXCRV_POOL_ID) - console.log("----earmark 2----") +let oracleRegistry, cdpManager, wrappedToUnderlyingOracle, unitParameters - await rewardPool.earned(cvxcrvLPWhaleAddress).then(a => console.log("claimable", BigInt(a), 'CRV')) +const duckDepositAmount = 10n ** 6n * 10n ** 18n - await crv.balanceOf(rewardPool.address).then(a=>console.log("crv at reward pool", BigInt(a))) - await cvx.balanceOf(rewardPool.address).then(a=>console.log("cvx at reward pool", BigInt(a))) +describe("Functional test", function () { - console.log("advance time...") - await ethers.provider.send('evm_mine', [await chainTime() + 7 * DAY]) - await rewardPool.earned(cvxcrvLPWhaleAddress).then(a => console.log("claimable", BigInt(a), 'CRV')) - await rewardPool.connect(cvxcrvLPWhale)['getReward()']() + before(async function () { + ;[admin, user1, user2, user3] = await ethers.getSigners() - crvBalance = await crv.balanceOf(cvxcrvLPWhaleAddress) - cvxBalance = await cvx.balanceOf(cvxcrvLPWhaleAddress) - expect(BigInt(cvxBalance) > 0n).to.be.true - console.log('claimed: CRV:', BigInt(crvBalance), 'CVX:', BigInt(cvxBalance)) - }) + duck = await ethers.getContractAt("IERC20", duckAddress) + veDuck = await ethers.getContractAt("IVoteEscrow", veDuckAddress) + usdp = await ethers.getContractAt("IERC20", usdpAddress) + veDistribution = await ethers.getContractAt("IVeDistribution", veDistributionAddress) + + oracleRegistry = await ethers.getContractAt("IOracleRegistry", '0x75fBFe26B21fd3EA008af0C764949f8214150C8f') + cdpManager = await ethers.getContractAt("ICDPManager01", '0x69FB4D4e3404Ea023F940bbC547851681e893a91') + wrappedToUnderlyingOracle = await ethers.getContractAt("IWrappedToUnderlyingOracle", '0x220Ea780a484c18fd0Ab252014c58299759a1Fbd') + unitParameters = await ethers.getContractAt("IParametersBatchUpdater", '0x4DD1A6DB148BEcDADAdFC407D23b725eDd3cfB6f') + + await ethers.provider.send("hardhat_impersonateAccount", [duckWhaleAddress]); + duckWhale = await ethers.getSigner(duckWhaleAddress) + await ethers.provider.send("hardhat_setBalance", [duckWhaleAddress, '0x3635c9adc5dea00000' /* 1000Ether */]); + + await ethers.provider.send("hardhat_impersonateAccount", [unitMultisigAddress]); + unitMultisig = await ethers.getSigner(unitMultisigAddress) + await ethers.provider.send("hardhat_setBalance", [unitMultisigAddress, '0x3635c9adc5dea00000' /* 1000Ether */]); + + ( + { + voteProxy, + booster, + uveDuck, + duckDepositor, + allowList + } = await deploy() + ); + + await veDuck.connect(unitMultisig).commit_smart_wallet_checker(allowList.address); + await veDuck.connect(unitMultisig).apply_smart_wallet_checker(); + await allowList.connect(unitMultisig).approveWallet(voteProxy.address); + + await booster.setTreasury(treasuryAddress); + + suveDuck = await ethers.getContractAt("FeePool", await booster.lockFees()) + console.log('suveDuck: ', suveDuck.address); + }) + + it("DUCK -> UVEDUCK", async function () { + + await duck.connect(duckWhale).transfer(voteProxy.address, 1) // transfers on voteproxy some duck for initial lock + + await duckDepositor.initialLock() + await duck.connect(duckWhale).approve(duckDepositor.address, duckDepositAmount) + + const depositResult1 = await duckDepositor.connect(duckWhale)['deposit(uint256,address)'](duckDepositAmount, suveDuck.address) + console.log('Deposit gas used', (await depositResult1.wait()).gasUsed.toString()) + }) + + it("usdp claim for UVEDUCK provided as collateral on Unit", async function () { + + // add SUVEDUCK as collateral on Unit Protocol (unit side) + await oracleRegistry.connect(unitMultisig).setOracleTypeForAsset(suveDuck.address, 11) + // duck uses keydonix oracle ATM (we can't test it with hardhat) + // so for test reasons we use weth price + await wrappedToUnderlyingOracle.connect(unitMultisig).setUnderlying(suveDuck.address, wethAddress) + await unitParameters.connect(unitMultisig).setCollaterals([suveDuck.address], 0, 0, 50, 75, 0, 1100, 10n ** 23n, [11]) + + // provide collateral and mint some USDP + await suveDuck.connect(duckWhale).approve(unitVaultAddress, duckDepositAmount) + await usdp.connect(duckWhale).approve(cdpManager.address, 10n ** 20n) + await cdpManager.connect(duckWhale).join(suveDuck.address, duckDepositAmount, 10n ** 21n) + + await usdp.connect(duckWhale).transfer(user2.address, 10n**20n) + + // usdp claim + await booster.connect(user3).earmarkFees(); + console.log("----------- initial fees earmarked") + await printBalances(); + + await ethers.provider.send('evm_mine', [await chainTime() + WEEK + WEEK + DAY]) + console.log("----------- 2 weeks later") + + // send some fees to veDsitribution + await usdp.connect(user2).transfer(veDistribution.address, 10n**20n); + await veDistribution.checkpoint_token(); + await veDistribution.checkpoint_total_supply(); + console.log("----------- fees sent to veDistributions") + + expect(await usdp.balanceOf(treasuryAddress)).to.be.equal(0) + expect(await usdp.balanceOf(user3.address)).to.be.equal(0) + await printBalances(); + await booster.connect(user3).earmarkFees() + console.log("----------- fees earmarked") + await printBalances(); + const treasuryUsdpBalance = await usdp.balanceOf(treasuryAddress) + const senderUsdpBalance = await usdp.balanceOf(user3.address) + const suveDuckUsdpBalance = await usdp.balanceOf(suveDuck.address) + expect(treasuryUsdpBalance).to.be.closeTo(senderUsdpBalance.mul(10), 100) // treasury 10%, sender 1% + expect(senderUsdpBalance.add(suveDuckUsdpBalance)).to.be.closeTo(treasuryUsdpBalance.mul(9), 100) // rest 89% sent to distribution 1+89 = 10*9 + + + await ethers.provider.send('evm_mine', [await chainTime() + DAY]) + console.log("----------- 1 day later"); + await printBalances(); + + await ethers.provider.send('evm_mine', [await chainTime() + DAY]) + console.log("----------- 1 day later"); + await printBalances(); + + const balanceBefore = BigInt(await usdp.balanceOf(duckWhaleAddress)) + await suveDuck.connect(duckWhale)['getReward()']() + const balanceAfter = BigInt(await usdp.balanceOf(duckWhaleAddress)) + console.log('----------- reward claimed', balanceAfter - balanceBefore, 'usdp') + await printBalances(); + + await ethers.provider.send('evm_mine', [await chainTime() + 5 * DAY]) + console.log("----------- 5 days later"); + await suveDuck.connect(duckWhale)['getReward()']() + const balanceAfter2 = BigInt(await usdp.balanceOf(duckWhaleAddress)) + console.log('----------- total reward claimed', balanceAfter2 - balanceBefore, 'usdp') + await printBalances(); + expect(balanceAfter2 - balanceBefore).to.be.closeTo(suveDuckUsdpBalance, 1_000_000) + }) }) -const chainTime = async () => (await ethers.provider.getBlock('latest')).timestamp \ No newline at end of file +const chainTime = async () => (await ethers.provider.getBlock('latest')).timestamp + +async function printBalances() { + await suveDuck.currentRewards().then(a => console.log("== suveDuck currentRewards: ", BigInt(a))) + await usdp.balanceOf(suveDuck.address).then(a => console.log("== suveDuck usdp balance: ", BigInt(a))) + await usdp.balanceOf(duckWhale.address).then(a => console.log("== user usdp balance: ", BigInt(a))) + await suveDuck.earned(duckWhaleAddress).then(a => console.log("== user earned fees: ", BigInt(a))) +} \ No newline at end of file