From b15951429a2ed17c9b9face1a71b1a5ce4e84f1e Mon Sep 17 00:00:00 2001 From: 0xmaharishi Date: Sun, 29 May 2022 14:08:25 +0100 Subject: [PATCH 01/20] feat: aura --- contracts/Aura.sol | 22 ++++++++-------------- contracts/AuraMinter.sol | 2 +- scripts/deploySystem.ts | 8 ++++---- test/Aura.spec.ts | 14 +++++++------- test/AuraMath.spec.ts | 1 + test/AuraVestedEscrow.spec.ts | 18 +++++++++++++++--- 6 files changed, 36 insertions(+), 29 deletions(-) diff --git a/contracts/Aura.sol b/contracts/Aura.sol index 1c26dc4f..21ea7d15 100644 --- a/contracts/Aura.sol +++ b/contracts/Aura.sol @@ -1,10 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.11; -import { IERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/IERC20.sol"; import { ERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/ERC20.sol"; -import { SafeERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/utils/SafeERC20.sol"; -import { Address } from "@openzeppelin/contracts-0.8/utils/Address.sol"; import { AuraMath } from "./AuraMath.sol"; interface IStaker { @@ -18,14 +15,13 @@ interface IStaker { * distirbuted along a supply curve (cliffs etc). Fork of ConvexToken. */ contract AuraToken is ERC20 { - using SafeERC20 for IERC20; - using Address for address; using AuraMath for uint256; address public operator; address public immutable vecrvProxy; uint256 public constant EMISSIONS_MAX_SUPPLY = 5e25; // 50m + uint256 public constant INIT_MINT_AMOUNT = 5e25; // 50m uint256 public constant totalCliffs = 500; uint256 public immutable reductionPerCliff; @@ -55,20 +51,14 @@ contract AuraToken is ERC20 { /** * @dev Initialise and mints initial supply of tokens. * @param _to Target address to mint. - * @param _amount Amount of tokens to mint. * @param _minter The minter address. */ - function init( - address _to, - uint256 _amount, - address _minter - ) external { + function init(address _to, address _minter) external { require(msg.sender == operator, "Only operator"); require(totalSupply() == 0, "Only once"); - require(_amount > 0, "Must mint something"); require(_minter != address(0), "Invalid minter"); - _mint(_to, _amount); + _mint(_to, INIT_MINT_AMOUNT); updateOperator(); minter = _minter; minterMinted = 0; @@ -80,7 +70,11 @@ contract AuraToken is ERC20 { * @dev This can be called if the operator of the voterProxy somehow changes. */ function updateOperator() public { + require(totalSupply() != 0, "!init"); + address newOperator = IStaker(vecrvProxy).operator(); + require(newOperator != operator && newOperator != address(0), "!operator"); + emit OperatorChanged(operator, newOperator); operator = newOperator; } @@ -98,7 +92,7 @@ contract AuraToken is ERC20 { } // e.g. emissionsMinted = 6e25 - 5e25 - 0 = 1e25; - uint256 emissionsMinted = totalSupply() - EMISSIONS_MAX_SUPPLY - minterMinted; + uint256 emissionsMinted = totalSupply() - INIT_MINT_AMOUNT - minterMinted; // e.g. reductionPerCliff = 5e25 / 500 = 1e23 // e.g. cliff = 1e25 / 1e23 = 100 uint256 cliff = emissionsMinted.div(reductionPerCliff); diff --git a/contracts/AuraMinter.sol b/contracts/AuraMinter.sol index da3a397e..521077d4 100644 --- a/contracts/AuraMinter.sol +++ b/contracts/AuraMinter.sol @@ -7,7 +7,7 @@ import { AuraToken } from "./Aura.sol"; /** * @title AuraMinter * @notice Wraps the AuraToken minterMint function and protects from inflation until - * 4 years have passed. + * 3 years have passed. * @dev Ownership initially owned by the DAO, but likely transferred to smart contract * wrapper or additional value system at some stage as directed by token holders. */ diff --git a/scripts/deploySystem.ts b/scripts/deploySystem.ts index 91c3be33..5afd0b8f 100644 --- a/scripts/deploySystem.ts +++ b/scripts/deploySystem.ts @@ -326,7 +326,7 @@ async function deployPhase2( .reduce((p, c) => p.add(c.recipients.reduce((pp, cc) => pp.add(cc.amount), BN.from(0))), BN.from(0)); const premine = premineIncetives.add(totalVested); const checksum = premine.add(distroList.miningRewards); - if (!checksum.eq(simpleToExactAmount(100, 24))) { + if (!checksum.eq(simpleToExactAmount(100, 24)) || !premine.eq(simpleToExactAmount(50, 24))) { console.log(checksum.toString()); throw console.error(); } @@ -581,7 +581,7 @@ async function deployPhase2( tx = await voterProxy.setOperator(booster.address); await waitForTx(tx, debug, waitForBlocks); - tx = await cvx.init(deployerAddress, premine.toString(), minter.address); + tx = await cvx.init(deployerAddress, minter.address); await waitForTx(tx, debug, waitForBlocks); tx = await stashFactory.setImplementation(ZERO_ADDRESS, ZERO_ADDRESS, stashV3.address); @@ -744,7 +744,7 @@ async function deployPhase2( tokens: poolTokens, name: `Balancer ${await cvxCrv.symbol()} Stable Pool`, symbol: `B-${await cvxCrv.symbol()}-STABLE`, - swapFee: simpleToExactAmount(3, 15), + swapFee: simpleToExactAmount(6, 15), ampParameter: 25, }; if (debug) { @@ -1020,7 +1020,7 @@ async function deployPhase3( tokens: poolTokens, name: `Balancer 80 ${await cvx.symbol()} 20 WETH`, symbol: `B-80${await cvx.symbol()}-20WETH`, - swapFee: simpleToExactAmount(6, 15), + swapFee: simpleToExactAmount(1, 16), weights: weights as BN[], }; if (debug) { diff --git a/test/Aura.spec.ts b/test/Aura.spec.ts index cdb49a36..0465b4a4 100644 --- a/test/Aura.spec.ts +++ b/test/Aura.spec.ts @@ -69,23 +69,23 @@ describe("AuraToken", () => { }); describe("@method AuraToken.init fails if ", async () => { it("caller is not the operator", async () => { - await expect(cvx.connect(deployer).init(DEAD_ADDRESS, 0, DEAD_ADDRESS)).to.revertedWith("Only operator"); + await expect(cvx.connect(deployer).init(DEAD_ADDRESS, DEAD_ADDRESS)).to.revertedWith("Only operator"); }); it("called more than once", async () => { - await expect(cvx.init(DEAD_ADDRESS, 0, DEAD_ADDRESS)).to.revertedWith("Only operator"); + await expect(cvx.init(DEAD_ADDRESS, DEAD_ADDRESS)).to.revertedWith("Only operator"); }); it("wrong amount of tokens", async () => { - await expect(cvx.init(DEAD_ADDRESS, 0, DEAD_ADDRESS)).to.revertedWith("Only operator"); + await expect(cvx.init(DEAD_ADDRESS, DEAD_ADDRESS)).to.revertedWith("Only operator"); }); it("wrong minter address", async () => { - await expect(cvx.init(DEAD_ADDRESS, 0, DEAD_ADDRESS)).to.revertedWith("Only operator"); + await expect(cvx.init(DEAD_ADDRESS, DEAD_ADDRESS)).to.revertedWith("Only operator"); }); }); - it("@method AuraToken.updateOperator sets new operator", async () => { + it("@method AuraToken.updateOperator fails to set new operator", async () => { const previousOperator = await cvx.operator(); - const tx = cvx.connect(deployer).updateOperator(); - await expect(tx).to.emit(cvx, "OperatorChanged").withArgs(previousOperator, booster.address); + expect(previousOperator).eq(booster.address); + await expect(cvx.connect(deployer).updateOperator()).to.be.revertedWith("!operator"); }); it("@method AuraToken.mint does not mint if sender is not the operator", async () => { const beforeBalance = await cvx.balanceOf(aliceAddress); diff --git a/test/AuraMath.spec.ts b/test/AuraMath.spec.ts index 764166bd..f6eaf572 100644 --- a/test/AuraMath.spec.ts +++ b/test/AuraMath.spec.ts @@ -60,6 +60,7 @@ describe("library AuraMath", () => { 0, ); expect(await auraMath.AuraMath_div(2, 1)).to.eq(2); + expect(await auraMath.AuraMath_div(3, 2)).to.eq(1); expect(await auraMath.AuraMath_div(1, 1)).to.eq(1); expect(await auraMath.AuraMath_div(ethers.constants.MaxUint256, ethers.constants.MaxUint256)).to.eq(1); await expect(auraMath.AuraMath_div(-1, 2), "no negative numbers").to.reverted; diff --git a/test/AuraVestedEscrow.spec.ts b/test/AuraVestedEscrow.spec.ts index e007cb02..9b60938d 100644 --- a/test/AuraVestedEscrow.spec.ts +++ b/test/AuraVestedEscrow.spec.ts @@ -4,7 +4,7 @@ import { expect } from "chai"; import { deployPhase1, deployPhase2, Phase2Deployed } from "../scripts/deploySystem"; import { deployMocks, getMockDistro, getMockMultisigs } from "../scripts/deployMocks"; import { AuraLocker, AuraVestedEscrow, AuraVestedEscrow__factory, ERC20 } from "../types/generated"; -import { ONE_WEEK, ZERO_ADDRESS } from "../test-utils/constants"; +import { ONE_HOUR, ONE_WEEK, ZERO_ADDRESS } from "../test-utils/constants"; import { getTimestamp, increaseTime } from "../test-utils/time"; import { BN, simpleToExactAmount } from "../test-utils/math"; import { impersonateAccount } from "../test-utils/fork"; @@ -39,8 +39,17 @@ describe("AuraVestedEscrow", () => { const multisigs = await getMockMultisigs(accounts[0], accounts[0], accounts[0]); const distro = getMockDistro(); - const phase1 = await deployPhase1(hre, deployer, mocks.addresses); - contracts = await deployPhase2(hre, deployer, phase1, distro, multisigs, mocks.namingConfig, mocks.addresses); + const phase1 = await deployPhase1(hre, deployer, mocks.addresses, true, true); + contracts = await deployPhase2( + hre, + deployer, + phase1, + distro, + multisigs, + mocks.namingConfig, + mocks.addresses, + true, + ); deployerAddress = await deployer.getAddress(); @@ -147,6 +156,9 @@ describe("AuraVestedEscrow", () => { const balAfter = await auraLocker.balances(aliceAddress); await expect(tx).to.emit(vestedEscrow, "Claim").withArgs(aliceAddress, balAfter.locked, true); + + await increaseTime(ONE_HOUR); + await vestedEscrow.connect(alice).claim(true); }); it("fails to cancel if not admin", async () => { await expect(vestedEscrow.connect(alice).cancel(bobAddress)).to.be.revertedWith("!auth"); From 1912d6210cdda847aad39ab73a71189c6bb8d783 Mon Sep 17 00:00:00 2001 From: 0xmaharishi Date: Sun, 29 May 2022 17:54:20 +0100 Subject: [PATCH 02/20] feat: auraBalRewardPool --- contracts/AuraBalRewardPool.sol | 35 ++++++++++++++++++++++++---- test/AuraBalRewardPool.spec.ts | 41 +++++++++++++++++++++++++++++---- 2 files changed, 66 insertions(+), 10 deletions(-) diff --git a/contracts/AuraBalRewardPool.sol b/contracts/AuraBalRewardPool.sol index ab75db87..76249544 100644 --- a/contracts/AuraBalRewardPool.sol +++ b/contracts/AuraBalRewardPool.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.11; import { AuraMath } from "./AuraMath.sol"; -import { SafeMath } from "@openzeppelin/contracts-0.8/utils/math/SafeMath.sol"; import { IERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/utils/SafeERC20.sol"; @@ -18,7 +17,7 @@ import { IAuraLocker } from "./Interfaces.sol"; * - Penalty on claim at 20% */ contract AuraBalRewardPool { - using SafeMath for uint256; + using AuraMath for uint256; using SafeERC20 for IERC20; IERC20 public immutable rewardToken; @@ -27,7 +26,7 @@ contract AuraBalRewardPool { address public immutable rewardManager; - IAuraLocker public immutable auraLocker; + IAuraLocker public auraLocker; address public immutable penaltyForwarder; uint256 public pendingPenalty = 0; uint256 public immutable startTime; @@ -47,6 +46,7 @@ contract AuraBalRewardPool { event Withdrawn(address indexed user, uint256 amount); event RewardPaid(address indexed user, uint256 reward, bool locked); event PenaltyForwarded(uint256 amount); + event Rescued(); /** * @dev Simple constructoor @@ -64,14 +64,17 @@ contract AuraBalRewardPool { address _penaltyForwarder, uint256 _startDelay ) { + require(_stakingToken != _rewardToken && _stakingToken != address(0), "!tokens"); stakingToken = IERC20(_stakingToken); rewardToken = IERC20(_rewardToken); + require(_rewardManager != address(0), "!manager"); rewardManager = _rewardManager; + require(_auraLocker != address(0), "!locker"); auraLocker = IAuraLocker(_auraLocker); + require(_penaltyForwarder != address(0), "!locker"); penaltyForwarder = _penaltyForwarder; - rewardToken.safeApprove(_auraLocker, type(uint256).max); - require(_startDelay < 2 weeks, "!delay"); + require(_startDelay > 4 days && _startDelay < 2 weeks, "!delay"); startTime = block.timestamp + _startDelay; } @@ -175,6 +178,7 @@ contract AuraBalRewardPool { if (reward > 0) { rewards[msg.sender] = 0; if (_lock) { + rewardToken.safeIncreaseAllowance(address(auraLocker), reward); auraLocker.lock(msg.sender, reward); } else { uint256 penalty = (reward * 2) / 10; @@ -196,6 +200,27 @@ contract AuraBalRewardPool { emit PenaltyForwarded(toForward); } + /** + * @dev Rescues the reward token provided it hasn't been initiated yet + */ + function rescueReward() public { + require(msg.sender == rewardManager, "!rescuer"); + require(block.timestamp < startTime && rewardRate == 0, "Already started"); + + uint256 balance = rewardToken.balanceOf(address(this)); + rewardToken.transfer(rewardManager, balance); + + emit Rescued(); + } + + /** + * @dev Updates the locker address + */ + function setLocker(address _newLocker) external { + require(msg.sender == rewardManager, "!auth"); + auraLocker = IAuraLocker(_newLocker); + } + /** * @dev Called once to initialise the rewards based on balance of stakeToken */ diff --git a/test/AuraBalRewardPool.spec.ts b/test/AuraBalRewardPool.spec.ts index 7b5c78d8..ccc44b5c 100644 --- a/test/AuraBalRewardPool.spec.ts +++ b/test/AuraBalRewardPool.spec.ts @@ -1,10 +1,17 @@ import hre, { ethers } from "hardhat"; import { Signer } from "ethers"; import { expect } from "chai"; -import { deployPhase1, deployPhase2, deployPhase3, deployPhase4, SystemDeployed } from "../scripts/deploySystem"; +import { + deployPhase1, + deployPhase2, + deployPhase3, + deployPhase4, + MultisigConfig, + SystemDeployed, +} from "../scripts/deploySystem"; import { deployMocks, getMockDistro, getMockMultisigs } from "../scripts/deployMocks"; import { AuraBalRewardPool, AuraBalRewardPool__factory, ERC20 } from "../types/generated"; -import { ONE_DAY, ONE_WEEK, ZERO_ADDRESS } from "../test-utils/constants"; +import { DEAD_ADDRESS, ONE_DAY, ONE_WEEK, ZERO_ADDRESS } from "../test-utils/constants"; import { increaseTime, getTimestamp } from "../test-utils/time"; import { BN, simpleToExactAmount } from "../test-utils/math"; import { assertBNClose, assertBNClosePercent } from "../test-utils/assertions"; @@ -15,6 +22,7 @@ describe("AuraBalRewardPool", () => { let contracts: SystemDeployed; let rewards: AuraBalRewardPool; let cvxCrv: ERC20; + let multisigs: MultisigConfig; let deployer: Signer; @@ -30,7 +38,7 @@ describe("AuraBalRewardPool", () => { const reset = async () => { const mocks = await deployMocks(hre, deployer); - const multisigs = await getMockMultisigs(accounts[0], accounts[0], accounts[0]); + multisigs = await getMockMultisigs(accounts[0], accounts[7], accounts[0]); const distro = getMockDistro(); const phase1 = await deployPhase1(hre, deployer, mocks.addresses); const phase2 = await deployPhase2( @@ -109,7 +117,7 @@ describe("AuraBalRewardPool", () => { it("initial configuration is correct", async () => { expect(await rewards.stakingToken()).eq(cvxCrv.address); expect(await rewards.rewardToken()).eq(contracts.cvx.address); - expect(await rewards.rewardManager()).eq(await deployer.getAddress()); + expect(await rewards.rewardManager()).eq(multisigs.treasuryMultisig); expect(await rewards.auraLocker()).eq(contracts.cvxLocker.address); expect(await rewards.penaltyForwarder()).eq(contracts.penaltyForwarder.address); const currentTime = await getTimestamp(); @@ -222,7 +230,7 @@ describe("AuraBalRewardPool", () => { await expect(rewards.connect(bob).initialiseRewards()).to.be.revertedWith("!authorized"); }); it("allows rewardManager to start process early", async () => { - const tx = await rewards.connect(deployer).initialiseRewards(); + const tx = await rewards.connect(accounts[7]).initialiseRewards(); await expect(tx).to.emit(rewards, "RewardAdded").withArgs(rewardAmount); }); it("only allows funding to be called once, ever", async () => { @@ -240,6 +248,29 @@ describe("AuraBalRewardPool", () => { ); await expect(rewardPool.connect(deployer).initialiseRewards()).to.be.revertedWith("!balance"); }); + it("fails to rescue after rewards start", async () => { + await expect(rewards.connect(bob).rescueReward()).to.be.revertedWith("!rescuer"); + await expect(rewards.connect(accounts[7]).rescueReward()).to.be.revertedWith("Already started"); + }); + }); + describe("rescuing", () => { + before(async () => { + await reset(); + }); + it("rescues rewards before contract has started", async () => { + const treasuryAddress = await accounts[7].getAddress(); + const contractBal = await contracts.cvx.balanceOf(rewards.address); + expect(contractBal).gt(0); + const treasuryBal = await contracts.cvx.balanceOf(treasuryAddress); + await rewards.connect(accounts[7]).rescueReward(); + const treasuryBalAfter = await contracts.cvx.balanceOf(treasuryAddress); + expect(treasuryBalAfter).eq(treasuryBal.add(contractBal)); + }); + it("allows admin to update auraLocker address", async () => { + await expect(rewards.connect(deployer).setLocker(DEAD_ADDRESS)).to.be.revertedWith("!auth"); + await rewards.connect(accounts[7]).setLocker(DEAD_ADDRESS); + expect(await rewards.auraLocker()).eq(DEAD_ADDRESS); + }); }); describe("fails", () => { it("if stake amount is zero", async () => { From 994a837d1a1fd297121e5d82a9805ded5e331ca2 Mon Sep 17 00:00:00 2001 From: 0xmaharishi Date: Sun, 29 May 2022 18:00:59 +0100 Subject: [PATCH 03/20] feat: auraClaimZap --- contracts/AuraClaimZap.sol | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/contracts/AuraClaimZap.sol b/contracts/AuraClaimZap.sol index 339a348e..0a4b375c 100644 --- a/contracts/AuraClaimZap.sol +++ b/contracts/AuraClaimZap.sol @@ -206,15 +206,13 @@ contract AuraClaimZap { } //stake up to given amount of cvx - if (depositCvxMaxAmount > 0) { + if (depositCvxMaxAmount > 0 && _checkOption(options, uint256(Options.LockCvx))) { uint256 cvxBalance = IERC20(cvx).balanceOf(msg.sender).sub(removeCvxBalance); cvxBalance = AuraMath.min(cvxBalance, depositCvxMaxAmount); if (cvxBalance > 0) { //pull cvx IERC20(cvx).safeTransferFrom(msg.sender, address(this), cvxBalance); - if (_checkOption(options, uint256(Options.LockCvx))) { - IAuraLocker(locker).lock(msg.sender, cvxBalance); - } + IAuraLocker(locker).lock(msg.sender, cvxBalance); } } } From 18585e7dbd5cc50b39b0ddb4538662ebb114c45c Mon Sep 17 00:00:00 2001 From: 0xmaharishi Date: Sun, 29 May 2022 21:03:48 +0100 Subject: [PATCH 04/20] feat: auraLocker --- contracts/AuraLocker.sol | 95 +++++++---- contracts/AuraStakingProxy.sol | 4 +- contracts/Interfaces.sol | 4 +- contracts/mocks/MockAuraLockor.sol | 40 +++++ test-fork/FullDeployment.spec.ts | 2 +- test-fork/RewardPoolDepositWrapper.spec.ts | 2 +- test/AuraLocker.spec.ts | 177 ++++++++++++++------- 7 files changed, 227 insertions(+), 97 deletions(-) create mode 100644 contracts/mocks/MockAuraLockor.sol diff --git a/contracts/AuraLocker.sol b/contracts/AuraLocker.sol index c9fbf405..295bcbed 100644 --- a/contracts/AuraLocker.sol +++ b/contracts/AuraLocker.sol @@ -1,6 +1,5 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.11; -pragma experimental ABIEncoderV2; import { IERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/utils/SafeERC20.sol"; @@ -69,7 +68,7 @@ contract AuraLocker is ReentrancyGuard, Ownable, IAuraLocker { // Rewards address[] public rewardTokens; - uint256 public queuedCvxCrvRewards = 0; + mapping(address => uint256) public queuedRewards; uint256 public constant newRewardRatio = 830; // Core reward data mapping(address => RewardData) public rewardData; @@ -100,6 +99,8 @@ contract AuraLocker is ReentrancyGuard, Ownable, IAuraLocker { mapping(address => mapping(uint256 => uint256)) public delegateeUnlocks; // Config + // Blacklisted smart contract interactions + mapping(address => bool) public blacklist; // Tokens IERC20 public immutable stakingToken; address public immutable cvxCrv; @@ -130,6 +131,7 @@ contract AuraLocker is ReentrancyGuard, Ownable, IAuraLocker { event KickReward(address indexed _user, address indexed _kicked, uint256 _reward); event RewardAdded(address indexed _token, uint256 _reward); + event BlacklistModified(address account, bool blacklisted); event KickIncentiveSet(uint256 rate, uint256 delay); event Shutdown(); @@ -187,14 +189,37 @@ contract AuraLocker is ReentrancyGuard, Ownable, IAuraLocker { _; } + modifier notBlacklisted(address _sender, address _receiver) { + uint256 csS; + uint256 csR; + assembly { + csS := extcodesize(_sender) + csR := extcodesize(_receiver) + } + if (csS != 0) { + require(!blacklist[_sender], "blacklisted"); + } + if (csR != 0) { + require(_sender == _receiver || !blacklist[_receiver], "blacklisted"); + } + _; + } + /*************************************** ADMIN ****************************************/ + function modifyBlacklist(address _account, bool _blacklisted) external onlyOwner { + blacklist[_account] = _blacklisted; + emit BlacklistModified(_account, _blacklisted); + } + // Add a new reward token to be distributed to stakers function addReward(address _rewardsToken, address _distributor) external onlyOwner { require(rewardData[_rewardsToken].lastUpdateTime == 0, "Reward already exists"); require(_rewardsToken != address(stakingToken), "Cannot add StakingToken as reward"); + require(rewardTokens.length < 5, "Max rewards length"); + rewardTokens.push(_rewardsToken); rewardData[_rewardsToken].lastUpdateTime = uint32(block.timestamp); rewardData[_rewardsToken].periodFinish = uint32(block.timestamp); @@ -255,7 +280,7 @@ contract AuraLocker is ReentrancyGuard, Ownable, IAuraLocker { } //lock tokens - function _lock(address _account, uint256 _amount) internal { + function _lock(address _account, uint256 _amount) internal notBlacklisted(msg.sender, _account) { require(_amount > 0, "Cannot stake 0"); require(!isShutdown, "shutdown"); @@ -318,6 +343,21 @@ contract AuraLocker is ReentrancyGuard, Ownable, IAuraLocker { } } + function getReward(address _account, bool[] calldata _skipIdx) external nonReentrant updateReward(_account) { + uint256 rewardTokensLength = rewardTokens.length; + require(_skipIdx.length == rewardTokensLength, "!arr"); + for (uint256 i; i < rewardTokensLength; i++) { + if (_skipIdx[i]) continue; + address _rewardsToken = rewardTokens[i]; + uint256 reward = userData[_account][_rewardsToken].rewards; + if (reward > 0) { + userData[_account][_rewardsToken].rewards = 0; + IERC20(_rewardsToken).safeTransfer(_account, reward); + emit RewardPaid(_account, _rewardsToken, reward); + } + } + } + function checkpointEpoch() external { _checkpointEpoch(); } @@ -325,14 +365,13 @@ contract AuraLocker is ReentrancyGuard, Ownable, IAuraLocker { //insert a new epoch if needed. fill in any gaps function _checkpointEpoch() internal { uint256 currentEpoch = block.timestamp.div(rewardsDuration).mul(rewardsDuration); - uint256 epochindex = epochs.length; //first epoch add in constructor, no need to check 0 length //check to add - if (epochs[epochindex - 1].date < currentEpoch) { - //fill any epoch gaps until the next epoch date. - while (epochs[epochs.length - 1].date != currentEpoch) { - uint256 nextEpochDate = uint256(epochs[epochs.length - 1].date).add(rewardsDuration); + uint256 nextEpochDate = uint256(epochs[epochs.length - 1].date); + if (nextEpochDate < currentEpoch) { + while (nextEpochDate != currentEpoch) { + nextEpochDate = nextEpochDate.add(rewardsDuration); epochs.push(Epoch({ supply: 0, date: uint32(nextEpochDate) })); } } @@ -401,7 +440,7 @@ contract AuraLocker is ReentrancyGuard, Ownable, IAuraLocker { uint256 currentEpoch = block.timestamp.sub(_checkDelay).div(rewardsDuration).mul(rewardsDuration); uint256 epochsover = currentEpoch.sub(uint256(locks[length - 1].unlockTime)).div(rewardsDuration); uint256 rRate = AuraMath.min(kickRewardPerEpoch.mul(epochsover + 1), denominator); - reward = uint256(locks[length - 1].amount).mul(rRate).div(denominator); + reward = uint256(locked).mul(rRate).div(denominator); } } else { //use a processed index(nextUnlockIndex) to not loop as much @@ -817,18 +856,20 @@ contract AuraLocker is ReentrancyGuard, Ownable, IAuraLocker { REWARD FUNDING ****************************************/ - function queueNewRewards(uint256 _rewards) external nonReentrant { - require(rewardDistributors[cvxCrv][msg.sender], "!authorized"); + function queueNewRewards(address _rewardsToken, uint256 _rewards) external nonReentrant { + require(rewardDistributors[_rewardsToken][msg.sender], "!authorized"); require(_rewards > 0, "No reward"); - RewardData storage rdata = rewardData[cvxCrv]; + RewardData storage rdata = rewardData[_rewardsToken]; - IERC20(cvxCrv).safeTransferFrom(msg.sender, address(this), _rewards); + IERC20(_rewardsToken).safeTransferFrom(msg.sender, address(this), _rewards); + + _rewards = _rewards.add(queuedRewards[_rewardsToken]); + require(_rewards < 1e25, "!rewards"); - _rewards = _rewards.add(queuedCvxCrvRewards); if (block.timestamp >= rdata.periodFinish) { - _notifyReward(cvxCrv, _rewards); - queuedCvxCrvRewards = 0; + _notifyReward(_rewardsToken, _rewards); + queuedRewards[_rewardsToken] = 0; return; } @@ -838,25 +879,13 @@ contract AuraLocker is ReentrancyGuard, Ownable, IAuraLocker { uint256 currentAtNow = rdata.rewardRate * elapsedTime; uint256 queuedRatio = currentAtNow.mul(1000).div(_rewards); if (queuedRatio < newRewardRatio) { - _notifyReward(cvxCrv, _rewards); - queuedCvxCrvRewards = 0; + _notifyReward(_rewardsToken, _rewards); + queuedRewards[_rewardsToken] = 0; } else { - queuedCvxCrvRewards = _rewards; + queuedRewards[_rewardsToken] = _rewards; } } - function notifyRewardAmount(address _rewardsToken, uint256 _reward) external { - require(_rewardsToken != cvxCrv, "Use queueNewRewards"); - require(rewardDistributors[_rewardsToken][msg.sender], "Must be rewardsDistributor"); - require(_reward > 0, "No reward"); - - _notifyReward(_rewardsToken, _reward); - - // handle the transfer of reward tokens via `transferFrom` to reduce the number - // of transactions required and ensure correctness of the _reward amount - IERC20(_rewardsToken).safeTransferFrom(msg.sender, address(this), _reward); - } - function _notifyReward(address _rewardsToken, uint256 _reward) internal updateReward(address(0)) { RewardData storage rdata = rewardData[_rewardsToken]; @@ -868,6 +897,10 @@ contract AuraLocker is ReentrancyGuard, Ownable, IAuraLocker { rdata.rewardRate = _reward.add(leftover).div(rewardsDuration).to96(); } + // Equivalent to 10 million tokens over a weeks duration + require(rdata.rewardRate < 1e20, "!rewardRate"); + require(lockedSupply >= 1e20, "!balance"); + rdata.lastUpdateTime = block.timestamp.to32(); rdata.periodFinish = block.timestamp.add(rewardsDuration).to32(); diff --git a/contracts/AuraStakingProxy.sol b/contracts/AuraStakingProxy.sol index 17d8a50d..859d252e 100644 --- a/contracts/AuraStakingProxy.sol +++ b/contracts/AuraStakingProxy.sol @@ -180,7 +180,7 @@ contract AuraStakingProxy { IERC20(cvxCrv).safeTransfer(msg.sender, incentiveAmount); //update rewards - IAuraLocker(rewards).queueNewRewards(cvxCrvBal); + IAuraLocker(rewards).queueNewRewards(cvxCrv, cvxCrvBal); emit RewardsDistributed(cvxCrv, cvxCrvBal); } @@ -206,7 +206,7 @@ contract AuraStakingProxy { _token.safeApprove(rewards, type(uint256).max); //update rewards - IAuraLocker(rewards).notifyRewardAmount(address(_token), bal); + IAuraLocker(rewards).queueNewRewards(address(_token), bal); emit RewardsDistributed(address(_token), bal); } diff --git a/contracts/Interfaces.sol b/contracts/Interfaces.sol index 9b410520..bdda9938 100644 --- a/contracts/Interfaces.sol +++ b/contracts/Interfaces.sol @@ -122,9 +122,7 @@ interface IAuraLocker { function totalSupplyAtEpoch(uint256 _epoch) external view returns (uint256 supply); - function queueNewRewards(uint256 _rewards) external; - - function notifyRewardAmount(address _rewardsToken, uint256 reward) external; + function queueNewRewards(address _rewardsToken, uint256 reward) external; function getReward(address _account, bool _stake) external; diff --git a/contracts/mocks/MockAuraLockor.sol b/contracts/mocks/MockAuraLockor.sol new file mode 100644 index 00000000..75f99904 --- /dev/null +++ b/contracts/mocks/MockAuraLockor.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.11; + +import { IERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/utils/SafeERC20.sol"; + +import { IAuraLocker } from "../Interfaces.sol"; + +/** + * @title AuraBalRewardPool + * @author Synthetix -> ConvexFinance -> adapted + * @dev Modifications from convex-platform/contracts/contracts/BaseRewardPool.sol: + * - Delayed start (tokens transferred then delay is enforced before notification) + * - One time duration of 14 days + * - Remove child reward contracts + * - Penalty on claim at 20% + */ +contract MockAuraLockor { + using SafeERC20 for IERC20; + + IERC20 public immutable aura; + IAuraLocker public immutable locker; + + constructor(address _aura, address _locker) { + aura = IERC20(_aura); + locker = IAuraLocker(_locker); + } + + function lock(uint256 _amount) external { + aura.safeTransferFrom(msg.sender, address(this), _amount); + aura.safeIncreaseAllowance(address(locker), _amount); + locker.lock(address(this), _amount); + } + + function lockFor(address _for, uint256 _amount) external { + aura.safeTransferFrom(msg.sender, address(this), _amount); + aura.safeIncreaseAllowance(address(locker), _amount); + locker.lock(_for, _amount); + } +} diff --git a/test-fork/FullDeployment.spec.ts b/test-fork/FullDeployment.spec.ts index 1eb9313d..194a20d2 100644 --- a/test-fork/FullDeployment.spec.ts +++ b/test-fork/FullDeployment.spec.ts @@ -440,7 +440,7 @@ describe("Full Deployment", () => { const { naming, multisigs } = config; expect(await cvxLocker.rewardTokens(0)).eq(cvxCrv.address); await expect(cvxLocker.rewardTokens(1)).to.be.reverted; - expect(await cvxLocker.queuedCvxCrvRewards()).eq(0); + expect(await cvxLocker.queuedRewards(cvxCrv.address)).eq(0); expect(await cvxLocker.rewardDistributors(cvxCrv.address, cvxStakingProxy.address)).eq(true); expect(await cvxLocker.lockedSupply()).eq(0); expect(await cvxLocker.stakingToken()).eq(cvx.address); diff --git a/test-fork/RewardPoolDepositWrapper.spec.ts b/test-fork/RewardPoolDepositWrapper.spec.ts index 11cd63e3..e380f4eb 100644 --- a/test-fork/RewardPoolDepositWrapper.spec.ts +++ b/test-fork/RewardPoolDepositWrapper.spec.ts @@ -17,7 +17,7 @@ import { config } from "../tasks/deploy/mainnet-config"; import { expect } from "chai"; import { JoinPoolRequestStruct } from "types/generated/IVault"; -const debug = false; +const debug = true; const usdcWhale = "0x47ac0Fb4F2D84898e4D9E7b4DaB3C24507a6D503"; const usdcAddress = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"; diff --git a/test/AuraLocker.spec.ts b/test/AuraLocker.spec.ts index 1c9861d2..65055c99 100644 --- a/test/AuraLocker.spec.ts +++ b/test/AuraLocker.spec.ts @@ -1,3 +1,4 @@ +import { MockAuraLockor } from "./../dist/types/generated/MockAuraLockor.d"; import { expect } from "chai"; import { ContractTransaction, Signer } from "ethers"; import hre, { ethers } from "hardhat"; @@ -24,6 +25,7 @@ import { Booster, CrvDepositor, CvxCrvToken, + MockAuraLockor__factory, MockERC20, MockERC20__factory, } from "../types/generated"; @@ -215,7 +217,7 @@ describe("AuraLocker", () => { const setup = async () => { mocks = await deployMocks(hre, deployer); - const multisigs = await getMockMultisigs(accounts[0], accounts[0], accounts[0]); + const multisigs = await getMockMultisigs(accounts[5], accounts[6], accounts[7]); const distro = getMockDistro(); const phase1 = await deployPhase1(hre, deployer, mocks.addresses); @@ -229,7 +231,7 @@ describe("AuraLocker", () => { mocks.addresses, ); const phase3 = await deployPhase3(hre, deployer, phase2, multisigs, mocks.addresses); - await phase3.poolManager.setProtectPool(false); + await phase3.poolManager.connect(accounts[7]).setProtectPool(false); const contracts = await deployPhase4(hre, deployer, phase3, mocks.addresses); alice = accounts[1]; @@ -290,7 +292,7 @@ describe("AuraLocker", () => { expect(await auraLocker.cvxCrv(), "AuraLocker cvxCrv").to.equal(cvxCrv.address); expect(await auraLocker.cvxcrvStaking(), "AuraLocker cvxCrvStaking").to.equal(cvxCrvRewards.address); expect(await auraLocker.epochCount(), "AuraLocker epoch counts").to.equal(1); - expect(await auraLocker.queuedCvxCrvRewards(), "AuraLocker lockDuration").to.equal(0); + expect(await auraLocker.queuedRewards(cvxCrv.address), "AuraLocker lockDuration").to.equal(0); expect(await auraLocker.rewardPerToken(cvxCrv.address), "AuraLocker rewardPerToken").to.equal(0); expect(await auraLocker.lastTimeRewardApplicable(cvxCrv.address), "cvxCrv lastTimeRewardApplicable").to.gt(0); // expect(await auraLocker.rewardTokens(0),"AuraLocker lockDuration").to.equal( 86400 * 7 * 17); @@ -423,6 +425,31 @@ describe("AuraLocker", () => { ).to.equal(simpleToExactAmount(100)); }); + it("notify rewards ", async () => { + const amount = simpleToExactAmount(100); + const mockToken = await deployContract( + hre, + new MockERC20__factory(deployer), + "mockToken", + ["mockToken", "mockToken", 18, await deployer.getAddress(), simpleToExactAmount(1000000)], + {}, + false, + ); + const distributor = accounts[3]; + const distributorAddress = await distributor.getAddress(); + + await mockToken.connect(deployer).approve(distributorAddress, amount); + await mockToken.connect(deployer).transfer(distributorAddress, amount); + await mockToken.connect(distributor).approve(auraLocker.address, amount); + + await auraLocker.connect(accounts[7]).addReward(mockToken.address, distributorAddress); + await auraLocker.connect(accounts[7]).approveRewardDistributor(mockToken.address, distributorAddress, true); + + const tx = await auraLocker.connect(distributor).queueNewRewards(mockToken.address, amount); + await expect(tx).to.emit(auraLocker, "RewardAdded").withArgs(mockToken.address, amount); + expect(await mockToken.balanceOf(auraLocker.address)).to.equal(amount); + }); + it("get rewards from CVX locker", async () => { await increaseTime(ONE_DAY.mul(105)); const cvxCrvBefore = await cvxCrv.balanceOf(aliceAddress); @@ -432,7 +459,7 @@ describe("AuraLocker", () => { dataBefore.account.claimableRewards[0].amount.div(100), ); - const tx = await auraLocker["getReward(address)"](aliceAddress); + const tx = await auraLocker["getReward(address,bool[])"](aliceAddress, [false, false]); const dataAfter = await getSnapShot(aliceAddress); await tx.wait(); @@ -464,29 +491,40 @@ describe("AuraLocker", () => { .emit(auraLocker, "Withdrawn") .withArgs(aliceAddress, dataBefore.account.balances.locked, relock); }); - it("notify rewards ", async () => { - const amount = simpleToExactAmount(100); - const mockToken = await deployContract( + }); + + context("smart contract deposits", () => { + let lockor: MockAuraLockor; + before(async () => { + lockor = await deployContract( hre, - new MockERC20__factory(deployer), - "mockToken", - ["mockToken", "mockToken", 18, await deployer.getAddress(), simpleToExactAmount(1000000)], + new MockAuraLockor__factory(deployer), + "Lockor", + [cvx.address, auraLocker.address], {}, false, ); - const distributor = accounts[3]; - const distributorAddress = await distributor.getAddress(); - await mockToken.connect(deployer).approve(distributorAddress, amount); - await mockToken.connect(deployer).transfer(distributorAddress, amount); - await mockToken.connect(distributor).approve(auraLocker.address, amount); - - await auraLocker.connect(deployer).addReward(mockToken.address, distributorAddress); - await auraLocker.connect(deployer).approveRewardDistributor(mockToken.address, distributorAddress, true); - - const tx = await auraLocker.connect(distributor).notifyRewardAmount(mockToken.address, amount); - await expect(tx).to.emit(auraLocker, "RewardAdded").withArgs(mockToken.address, amount); - expect(await mockToken.balanceOf(auraLocker.address)).to.equal(amount); + await cvx.connect(alice).approve(lockor.address, simpleToExactAmount(1000)); + await cvx.connect(alice).approve(auraLocker.address, simpleToExactAmount(1000)); + }); + it("allows smart contract deposits", async () => { + await lockor.connect(alice).lock(simpleToExactAmount(10)); + await lockor.connect(alice).lockFor(aliceAddress, simpleToExactAmount(10)); + }); + it("allows blacklisting", async () => { + const tx = await auraLocker.connect(accounts[7]).modifyBlacklist(aliceAddress, true); + await expect(tx).to.emit(auraLocker, "BlacklistModified").withArgs(aliceAddress, true); + expect(await auraLocker.blacklist(aliceAddress)).eq(true); + }); + it("still allows blacklisted EOA's to lock", async () => { + await auraLocker.connect(alice).lock(aliceAddress, simpleToExactAmount(10)); + }); + it("blocks contracts from depositing when they are blacklisted", async () => { + await auraLocker.connect(accounts[7]).modifyBlacklist(lockor.address, true); + await expect(lockor.connect(alice).lockFor(bobAddress, simpleToExactAmount(10))).to.be.revertedWith( + "blacklisted", + ); }); }); @@ -891,23 +929,41 @@ describe("AuraLocker", () => { }); it("fails if the sender is not rewardsDistributor", async () => { // Only the rewardsDistributor can queue cvxCRV rewards - await expect(auraLocker.queueNewRewards(simpleToExactAmount(100))).revertedWith("!authorized"); + await expect(auraLocker.queueNewRewards(cvxCrv.address, simpleToExactAmount(100))).revertedWith( + "!authorized", + ); }); it("fails if the amount of rewards is 0", async () => { // Only the rewardsDistributor can queue cvxCRV rewards await expect( - auraLocker.connect(cvxStakingProxyAccount.signer).queueNewRewards(simpleToExactAmount(0)), + auraLocker + .connect(cvxStakingProxyAccount.signer) + .queueNewRewards(cvxCrv.address, simpleToExactAmount(0)), ).revertedWith("No reward"); }); + it("fails if balance is too low", async () => { + await booster.earmarkRewards(boosterPoolId); + await increaseTime(ONE_DAY); + + const incentive = await booster.stakerIncentive(); + const rate = await mocks.crvMinter.rate(); + const stakingCrvBalance = await mocks.crv.balanceOf(cvxStakingProxy.address); + + expect(stakingCrvBalance).to.equal(rate.mul(incentive).div(10000)); + + await expect(cvxStakingProxy.distribute()).to.be.revertedWith("!balance"); + }); it("distribute rewards from the booster", async () => { + await auraLocker.connect(alice).lock(aliceAddress, simpleToExactAmount(100)); await distributeRewardsFromBooster(); }); it("queues rewards when cvxCrv period is finished", async () => { + await auraLocker.connect(alice).lock(aliceAddress, simpleToExactAmount(100)); // AuraStakingProxy.distribute(), faked by impersonating account let rewards = simpleToExactAmount(100); const rewardDistribution = await auraLocker.rewardsDuration(); const cvxCrvLockerBalance0 = await cvxCrv.balanceOf(auraLocker.address); - const queuedCvxCrvRewards0 = await auraLocker.queuedCvxCrvRewards(); + const queuedCvxCrvRewards0 = await auraLocker.queuedRewards(cvxCrv.address); const rewardData0 = await auraLocker.rewardData(cvxCrv.address); const timeStamp = await getTimestamp(); @@ -922,7 +978,7 @@ describe("AuraLocker", () => { expect(await cvxCrv.balanceOf(auraLocker.address), "cvxCrv is transfer to locker").to.eq( cvxCrvLockerBalance0.add(rewards), ); - expect(await auraLocker.queuedCvxCrvRewards(), "queued cvxCrv rewards").to.eq(0); + expect(await auraLocker.queuedRewards(cvxCrv.address), "queued cvxCrv rewards").to.eq(0); // Verify reward data is updated, reward rate, lastUpdateTime, periodFinish; when the lastUpdateTime is lt than now. expect(rewardData1.lastUpdateTime, "cvxCrv reward last update time").to.gt(rewardData0.lastUpdateTime); @@ -949,7 +1005,7 @@ describe("AuraLocker", () => { let rewards = await distributeRewardsFromBooster(); // Validate const cvxCrvLockerBalance1 = await cvxCrv.balanceOf(auraLocker.address); - const queuedCvxCrvRewards1 = await auraLocker.queuedCvxCrvRewards(); + const queuedCvxCrvRewards1 = await auraLocker.queuedRewards(cvxCrv.address); const rewardData1 = await auraLocker.rewardData(cvxCrv.address); // Verify reward data is updated, reward rate, lastUpdateTime, periodFinish; when the lastUpdateTime is lt than now. @@ -967,7 +1023,7 @@ describe("AuraLocker", () => { rewards = await distributeRewardsFromBooster(); const cvxCrvLockerBalance2 = await cvxCrv.balanceOf(auraLocker.address); - const queuedCvxCrvRewards2 = await auraLocker.queuedCvxCrvRewards(); + const queuedCvxCrvRewards2 = await auraLocker.queuedRewards(cvxCrv.address); const rewardData2 = await auraLocker.rewardData(cvxCrv.address); // Verify reward data is not updated, as ratio is not reached. @@ -985,7 +1041,7 @@ describe("AuraLocker", () => { rewards = await distributeRewardsFromBooster(); const cvxCrvLockerBalance3 = await cvxCrv.balanceOf(auraLocker.address); - const queuedCvxCrvRewards3 = await auraLocker.queuedCvxCrvRewards(); + const queuedCvxCrvRewards3 = await auraLocker.queuedRewards(cvxCrv.address); const rewardData3 = await auraLocker.rewardData(cvxCrv.address); // Verify reward data is updated, reward rate, lastUpdateTime, periodFinish; when the lastUpdateTime is lt than now. @@ -1076,15 +1132,15 @@ describe("AuraLocker", () => { delegate2 = await accounts[4].getAddress(); // Mint some cvxCRV and add as the reward token manually - let tx = await booster.earmarkRewards(boosterPoolId); + let tx = await cvx.connect(alice).approve(auraLocker.address, simpleToExactAmount(100)); await tx.wait(); - - tx = await cvxStakingProxy.distribute(); + tx = await auraLocker.connect(alice).lock(aliceAddress, simpleToExactAmount(100)); await tx.wait(); - tx = await cvx.connect(alice).approve(auraLocker.address, simpleToExactAmount(100)); + tx = await booster.earmarkRewards(boosterPoolId); await tx.wait(); - tx = await auraLocker.connect(alice).lock(aliceAddress, simpleToExactAmount(100)); + + tx = await cvxStakingProxy.distribute(); await tx.wait(); const lock = await auraLocker.userLocks(aliceAddress, 0); @@ -1298,13 +1354,10 @@ describe("AuraLocker", () => { before(async () => { await setup(); }); - it("@notifyRewardAmount adds cvxCrv", async () => { - await expect(auraLocker.notifyRewardAmount(cvxCrv.address, 0)).revertedWith("Use queueNewRewards"); - }); - it("notifyRewardAmount sender is not a distributor", async () => { - await expect(auraLocker.notifyRewardAmount(cvx.address, 0)).revertedWith("Must be rewardsDistributor"); + it("queueNewRewards sender is not a distributor", async () => { + await expect(auraLocker.queueNewRewards(cvx.address, 0)).revertedWith("!authorized"); }); - it("@notifyRewardAmount sends wrong amount", async () => { + it("@queueNewRewards sends wrong amount", async () => { const mockToken = await deployContract( hre, new MockERC20__factory(deployer), @@ -1314,11 +1367,11 @@ describe("AuraLocker", () => { false, ); const distributor = accounts[3]; - await auraLocker.connect(deployer).addReward(mockToken.address, await distributor.getAddress()); + await auraLocker.connect(accounts[7]).addReward(mockToken.address, await distributor.getAddress()); await auraLocker - .connect(deployer) + .connect(accounts[7]) .approveRewardDistributor(mockToken.address, await distributor.getAddress(), true); - await expect(auraLocker.connect(distributor).notifyRewardAmount(mockToken.address, 0)).revertedWith( + await expect(auraLocker.connect(distributor).queueNewRewards(mockToken.address, 0)).revertedWith( "No reward", ); }); @@ -1332,7 +1385,7 @@ describe("AuraLocker", () => { ); }); it("approves reward wrong arguments", async () => { - const tx = auraLocker.approveRewardDistributor(ZERO_ADDRESS, ZERO_ADDRESS, false); + const tx = auraLocker.connect(accounts[7]).approveRewardDistributor(ZERO_ADDRESS, ZERO_ADDRESS, false); await expect(tx).revertedWith("Reward does not exist"); }); it("non admin - shutdowns", async () => { @@ -1359,18 +1412,20 @@ describe("AuraLocker", () => { ); }); it("set Kick Incentive with wrong rate", async () => { - await expect(auraLocker.setKickIncentive(501, ZERO)).revertedWith("over max rate"); + await expect(auraLocker.connect(accounts[7]).setKickIncentive(501, ZERO)).revertedWith("over max rate"); }); it("set Kick Incentive with wrong delay", async () => { - await expect(auraLocker.setKickIncentive(100, 1)).revertedWith("min delay"); + await expect(auraLocker.connect(accounts[7]).setKickIncentive(100, 1)).revertedWith("min delay"); }); it("recover ERC20 with wrong token address", async () => { - await expect(auraLocker.recoverERC20(cvx.address, ZERO)).revertedWith("Cannot withdraw staking token"); + await expect(auraLocker.connect(accounts[7]).recoverERC20(cvx.address, ZERO)).revertedWith( + "Cannot withdraw staking token", + ); }); it("recover ERC20 cannot withdraw reward", async () => { - await auraLocker.addReward(cvxCrvRewards.address, cvxCrvRewards.address); + await auraLocker.connect(accounts[7]).addReward(cvxCrvRewards.address, cvxCrvRewards.address); expect((await auraLocker.rewardData(cvxCrvRewards.address)).lastUpdateTime).to.not.eq(0); - await expect(auraLocker.recoverERC20(cvxCrvRewards.address, ZERO)).revertedWith( + await expect(auraLocker.connect(accounts[7]).recoverERC20(cvxCrvRewards.address, ZERO)).revertedWith( "Cannot withdraw reward token", ); }); @@ -1387,15 +1442,19 @@ describe("AuraLocker", () => { await cvx.connect(alice).approve(auraLocker.address, cvxAmount); // approves distributor - await auraLocker.approveRewardDistributor(cvxCrv.address, cvxCrvRewards.address, true); + await auraLocker.connect(accounts[7]).approveRewardDistributor(cvxCrv.address, cvxCrvRewards.address, true); await expect(await auraLocker.rewardDistributors(cvxCrv.address, cvxCrvRewards.address)).to.eq(true); // disapproves distributor - await auraLocker.approveRewardDistributor(cvxCrv.address, cvxCrvRewards.address, false); + await auraLocker + .connect(accounts[7]) + .approveRewardDistributor(cvxCrv.address, cvxCrvRewards.address, false); await expect(await auraLocker.rewardDistributors(cvxCrv.address, cvxCrvRewards.address)).to.eq(false); }); it("set Kick Incentive", async () => { - await expect(auraLocker.setKickIncentive(100, 3)).emit(auraLocker, "KickIncentiveSet").withArgs(100, 3); + await expect(auraLocker.connect(accounts[7]).setKickIncentive(100, 3)) + .emit(auraLocker, "KickIncentiveSet") + .withArgs(100, 3); expect(await auraLocker.kickRewardPerEpoch()).to.eq(100); expect(await auraLocker.kickRewardEpochDelay()).to.eq(3); }); @@ -1412,13 +1471,13 @@ describe("AuraLocker", () => { await mockToken.connect(deployer).approve(auraLocker.address, simpleToExactAmount(100)); await mockToken.connect(deployer).transfer(auraLocker.address, simpleToExactAmount(10)); - const mockDeployerBalanceBefore = await mockToken.balanceOf(await deployer.getAddress()); + const mockDeployerBalanceBefore = await mockToken.balanceOf(await accounts[7].getAddress()); const mockLockerBalanceBefore = await mockToken.balanceOf(auraLocker.address); expect(mockLockerBalanceBefore, "locker external lp reward").to.eq(simpleToExactAmount(10)); - const tx = auraLocker.recoverERC20(mockToken.address, simpleToExactAmount(10)); + const tx = auraLocker.connect(accounts[7]).recoverERC20(mockToken.address, simpleToExactAmount(10)); await expect(tx).emit(auraLocker, "Recovered").withArgs(mockToken.address, simpleToExactAmount(10)); - const mockDeployerBalanceAfter = await mockToken.balanceOf(await deployer.getAddress()); + const mockDeployerBalanceAfter = await mockToken.balanceOf(await accounts[7].getAddress()); const mockLockerBalanceAfter = await mockToken.balanceOf(auraLocker.address); expect(mockLockerBalanceAfter, "locker external lp reward").to.eq(0); @@ -1433,7 +1492,7 @@ describe("AuraLocker", () => { }); it("fails if lock", async () => { // Given that the aura locker is shutdown - await auraLocker.connect(deployer).shutdown(); + await auraLocker.connect(accounts[7]).shutdown(); expect(await auraLocker.isShutdown()).to.eq(true); // Then it should fail to lock const cvxAmount = simpleToExactAmount(100); @@ -1450,7 +1509,7 @@ describe("AuraLocker", () => { await expect(auraLocker.connect(alice).processExpiredLocks(relock)).revertedWith("no exp locks"); // Given that the aura locker is shutdown - await auraLocker.connect(deployer).shutdown(); + await auraLocker.connect(accounts[7]).shutdown(); expect(await auraLocker.isShutdown()).to.eq(true); // Then it should be able to process unexpired locks @@ -1469,7 +1528,7 @@ describe("AuraLocker", () => { }); it("emergencyWithdraw when user has no locks", async () => { // Given that the aura locker is shutdown - await auraLocker.connect(deployer).shutdown(); + await auraLocker.connect(accounts[7]).shutdown(); expect(await auraLocker.isShutdown()).to.eq(true); // It fails if the user has no locks await expect(auraLocker.connect(alice).emergencyWithdraw()).revertedWith("Nothing locked"); @@ -1480,7 +1539,7 @@ describe("AuraLocker", () => { await cvx.connect(alice).approve(auraLocker.address, cvxAmount); let tx = await auraLocker.connect(alice).lock(aliceAddress, cvxAmount); // Given that the aura locker is shutdown - await auraLocker.connect(deployer).shutdown(); + await auraLocker.connect(accounts[7]).shutdown(); expect(await auraLocker.isShutdown()).to.eq(true); // Then it should be able to withdraw in an emergency const dataBefore = await getSnapShot(aliceAddress); From 6f17d8a450d2d07a9f6427f78c8fd732f30c3fbd Mon Sep 17 00:00:00 2001 From: 0xmaharishi Date: Sun, 29 May 2022 21:04:38 +0100 Subject: [PATCH 05/20] feat: auraMerkleDrop --- contracts/AuraMerkleDrop.sol | 24 +++++++++++++++++++++--- test/AuraMerkleDrop.spec.ts | 5 ----- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/contracts/AuraMerkleDrop.sol b/contracts/AuraMerkleDrop.sol index ab43c9fa..64d2a5fc 100644 --- a/contracts/AuraMerkleDrop.sol +++ b/contracts/AuraMerkleDrop.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.11; import { MerkleProof } from "@openzeppelin/contracts-0.8/utils/cryptography/MerkleProof.sol"; import { IAuraLocker } from "./Interfaces.sol"; +import { AuraMath } from "./AuraMath.sol"; import { IERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/utils/SafeERC20.sol"; import { ReentrancyGuard } from "@openzeppelin/contracts-0.8/security/ReentrancyGuard.sol"; @@ -28,6 +29,7 @@ contract AuraMerkleDrop { address public immutable penaltyForwarder; uint256 public pendingPenalty = 0; + uint256 public immutable deployTime; uint256 public startTime; uint256 public immutable expiryTime; @@ -40,6 +42,7 @@ contract AuraMerkleDrop { event LockerSet(address newLocker); event Claimed(address addr, uint256 amt, bool locked); event PenaltyForwarded(uint256 amount); + event Rescued(); /** * @param _dao The Aura Dao @@ -59,11 +62,15 @@ contract AuraMerkleDrop { uint256 _startDelay, uint256 _expiresAfter ) { + require(_dao != address(0), "!dao"); dao = _dao; merkleRoot = _merkleRoot; + require(_aura != address(0), "!aura"); aura = IERC20(_aura); auraLocker = IAuraLocker(_auraLocker); + penaltyForwarder = _penaltyForwarder; + deployTime = block.timestamp; startTime = block.timestamp + _startDelay; require(_expiresAfter > 2 weeks, "!expiry"); @@ -96,7 +103,7 @@ contract AuraMerkleDrop { function withdrawExpired() external { require(msg.sender == dao, "!auth"); require(block.timestamp > expiryTime, "!expired"); - uint256 amt = aura.balanceOf(address(this)); + uint256 amt = aura.balanceOf(address(this)) - pendingPenalty; aura.safeTransfer(dao, amt); emit ExpiredWithdrawn(amt); } @@ -107,6 +114,16 @@ contract AuraMerkleDrop { emit LockerSet(_newLocker); } + function rescueReward() public { + require(msg.sender == dao, "!auth"); + require(block.timestamp < AuraMath.min(deployTime + 1 weeks, startTime), "too late"); + + uint256 amt = aura.balanceOf(address(this)); + aura.safeTransfer(dao, amt); + + emit Rescued(); + } + /*************************************** CLAIM ****************************************/ @@ -133,7 +150,9 @@ contract AuraMerkleDrop { auraLocker.lock(msg.sender, _amount); } else { // If there is an address for auraLocker, and not locking, apply 20% penalty - uint256 penalty = address(auraLocker) == address(0) ? 0 : (_amount * 2) / 10; + uint256 penalty = address(penaltyForwarder) == address(0) || address(auraLocker) == address(0) + ? 0 + : (_amount * 2) / 10; pendingPenalty += penalty; aura.safeTransfer(msg.sender, _amount - penalty); } @@ -149,7 +168,6 @@ contract AuraMerkleDrop { function forwardPenalty() public { uint256 toForward = pendingPenalty; pendingPenalty = 0; - require(penaltyForwarder != address(0), "!forwarder"); aura.safeTransfer(penaltyForwarder, toForward); emit PenaltyForwarded(toForward); } diff --git a/test/AuraMerkleDrop.spec.ts b/test/AuraMerkleDrop.spec.ts index cca8c7c3..49b23020 100644 --- a/test/AuraMerkleDrop.spec.ts +++ b/test/AuraMerkleDrop.spec.ts @@ -291,11 +291,6 @@ describe("AuraMerkleDrop", () => { merkleDrop.connect(alice).claim(getAccountBalanceProof(tree, aliceAddress, amount), amount, lock), ).to.be.revertedWith("!active"); }); - it("forward penalty fails if penaltyForwarder is not set", async () => { - expect(await merkleDrop.penaltyForwarder(), "penaltyForwarder").to.eq(ZERO_ADDRESS); - // Test - await expect(merkleDrop.forwardPenalty()).to.be.revertedWith("!forwarder"); - }); }); describe("admin", () => { let tree: MerkleTree; From b058ddf00f1bc512029a37a944fa986a6bc2d1b1 Mon Sep 17 00:00:00 2001 From: 0xmaharishi Date: Sun, 29 May 2022 21:30:39 +0100 Subject: [PATCH 06/20] fix: ts error --- test-fork/RewardPoolDepositWrapper.spec.ts | 2 +- test/AuraLocker.spec.ts | 2 +- test/AuraStakingProxy.spec.ts | 5 ++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/test-fork/RewardPoolDepositWrapper.spec.ts b/test-fork/RewardPoolDepositWrapper.spec.ts index e380f4eb..11cd63e3 100644 --- a/test-fork/RewardPoolDepositWrapper.spec.ts +++ b/test-fork/RewardPoolDepositWrapper.spec.ts @@ -17,7 +17,7 @@ import { config } from "../tasks/deploy/mainnet-config"; import { expect } from "chai"; import { JoinPoolRequestStruct } from "types/generated/IVault"; -const debug = true; +const debug = false; const usdcWhale = "0x47ac0Fb4F2D84898e4D9E7b4DaB3C24507a6D503"; const usdcAddress = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"; diff --git a/test/AuraLocker.spec.ts b/test/AuraLocker.spec.ts index 65055c99..4dc62165 100644 --- a/test/AuraLocker.spec.ts +++ b/test/AuraLocker.spec.ts @@ -1,4 +1,3 @@ -import { MockAuraLockor } from "./../dist/types/generated/MockAuraLockor.d"; import { expect } from "chai"; import { ContractTransaction, Signer } from "ethers"; import hre, { ethers } from "hardhat"; @@ -25,6 +24,7 @@ import { Booster, CrvDepositor, CvxCrvToken, + MockAuraLockor, MockAuraLockor__factory, MockERC20, MockERC20__factory, diff --git a/test/AuraStakingProxy.spec.ts b/test/AuraStakingProxy.spec.ts index e1bfc010..aff2d68c 100644 --- a/test/AuraStakingProxy.spec.ts +++ b/test/AuraStakingProxy.spec.ts @@ -49,9 +49,12 @@ describe("AuraStakingProxy", () => { .mint(operatorAccount.address, simpleToExactAmount(100000, 18)); await tx.wait(); - tx = await contracts.cvx.connect(operatorAccount.signer).transfer(aliceAddress, simpleToExactAmount(200)); + tx = await contracts.cvx.connect(operatorAccount.signer).transfer(aliceAddress, simpleToExactAmount(300)); await tx.wait(); + await contracts.cvx.connect(alice).approve(contracts.cvxLocker.address, simpleToExactAmount(100)); + await contracts.cvxLocker.connect(alice).lock(aliceAddress, simpleToExactAmount(100)); + tx = await contracts.cvx.connect(operatorAccount.signer).transfer(bobAddress, simpleToExactAmount(100)); await tx.wait(); }; From 89d8333a13de5e4a9ee4d861f8b5b9aded7bac38 Mon Sep 17 00:00:00 2001 From: 0xmaharishi Date: Mon, 30 May 2022 11:53:48 +0100 Subject: [PATCH 07/20] feat: auraVestedEscrow --- contracts/AuraVestedEscrow.sol | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/contracts/AuraVestedEscrow.sol b/contracts/AuraVestedEscrow.sol index 231a1bc1..70989969 100644 --- a/contracts/AuraVestedEscrow.sol +++ b/contracts/AuraVestedEscrow.sol @@ -24,6 +24,7 @@ contract AuraVestedEscrow is ReentrancyGuard { IERC20 public immutable rewardToken; address public admin; + address public immutable funder; IAuraLocker public auraLocker; uint256 public immutable startTime; @@ -58,6 +59,7 @@ contract AuraVestedEscrow is ReentrancyGuard { rewardToken = IERC20(rewardToken_); admin = admin_; + funder = msg.sender; auraLocker = IAuraLocker(auraLocker_); startTime = starttime_; @@ -94,7 +96,10 @@ contract AuraVestedEscrow is ReentrancyGuard { * @param _amount Arrary of amount of rewardTokens to vest */ function fund(address[] calldata _recipient, uint256[] calldata _amount) external nonReentrant { + require(_recipient.length == _amount.length, "!arr"); require(!initialised, "initialised already"); + require(msg.sender == funder, "!funder"); + require(block.timestamp < startTime, "already started"); uint256 totalAmount = 0; for (uint256 i = 0; i < _recipient.length; i++) { From e7f36109f09640bb743f9bb598a92977426bf951 Mon Sep 17 00:00:00 2001 From: 0xmaharishi Date: Mon, 30 May 2022 12:08:42 +0100 Subject: [PATCH 08/20] feat: balLiquidityProvider --- contracts/BalLiquidityProvider.sol | 6 +++++- test/BalLiquidityProvider.spec.ts | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/contracts/BalLiquidityProvider.sol b/contracts/BalLiquidityProvider.sol index 827999bc..a952ad16 100644 --- a/contracts/BalLiquidityProvider.sol +++ b/contracts/BalLiquidityProvider.sol @@ -46,6 +46,7 @@ contract BalLiquidityProvider { function provideLiquidity(bytes32 _poolId, IVault.JoinPoolRequest memory _request) public { require(msg.sender == provider, "!auth"); require(_request.assets.length == 2 && _request.maxAmountsIn.length == 2, "!valid"); + require(_request.assets[0] != _request.assets[1], "!assets"); require(pairToken.balanceOf(address(this)) > minPairAmount, "!minLiq"); for (uint256 i = 0; i < 2; i++) { @@ -54,7 +55,10 @@ contract BalLiquidityProvider { IERC20 tkn = IERC20(asset); uint256 bal = tkn.balanceOf(address(this)); - require(bal > 0 && bal == _request.maxAmountsIn[i], "!bal"); + require( + bal > 0 && bal >= _request.maxAmountsIn[i] && bal <= (_request.maxAmountsIn[i] * 102) / 100, + "!bal" + ); tkn.safeApprove(address(bVault), 0); tkn.safeApprove(address(bVault), bal); diff --git a/test/BalLiquidityProvider.spec.ts b/test/BalLiquidityProvider.spec.ts index 37e12eb7..be2725a4 100644 --- a/test/BalLiquidityProvider.spec.ts +++ b/test/BalLiquidityProvider.spec.ts @@ -165,7 +165,7 @@ describe("BalLiquidityProvider", () => { // Given const startTokenBalance = await startTokenContract.balanceOf(balLiquidityProvider.address); // set a wrong max amount in start token. - joinPoolRequest.maxAmountsIn = [simpleToExactAmount(123), simpleToExactAmount(123)]; + joinPoolRequest.maxAmountsIn = [simpleToExactAmount(123000), simpleToExactAmount(123000)]; expect(startTokenBalance, "initial start token balance").to.not.eq(joinPoolRequest.maxAmountsIn[0]); // When await expect( From b48127bfe0fc2a4f4922fe894d102dd2cd330f3e Mon Sep 17 00:00:00 2001 From: 0xmaharishi Date: Mon, 30 May 2022 12:35:45 +0100 Subject: [PATCH 09/20] feat: baseRewardPool --- convex-platform | 2 +- yarn.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/convex-platform b/convex-platform index 235ae95f..b65977cf 160000 --- a/convex-platform +++ b/convex-platform @@ -1 +1 @@ -Subproject commit 235ae95f534081e12ae32fde565bb971a49ee507 +Subproject commit b65977cf01da64d5a5112f379cf54aba98059410 diff --git a/yarn.lock b/yarn.lock index bd5843de..5968d7d7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4560,10 +4560,10 @@ __metadata: "convex-platform@file:convex-platform/contracts::locator=%40aurafinance%2Faura-contracts%40workspace%3A.": version: 0.0.0 - resolution: "convex-platform@file:convex-platform/contracts#convex-platform/contracts::hash=71f792&locator=%40aurafinance%2Faura-contracts%40workspace%3A." + resolution: "convex-platform@file:convex-platform/contracts#convex-platform/contracts::hash=4ac14c&locator=%40aurafinance%2Faura-contracts%40workspace%3A." dependencies: "@openzeppelin/contracts-0.6": "npm:@openzeppelin/contracts@3.4.0" - checksum: c790b75a5e7dd80a74a3ac4acee17c5dbd84a1a6ac6c480bc3dff1817a35a8138b58490c513ad5b93d17adf6a81d7e430a0cc6bea4f3b17bfa6b61d86ea87082 + checksum: eefe37288431819dbe79b768537d3556b0ac10c0d5501ec0b7dee80e2b0b5f26d167f069c25fdaad94b1b86c9fcca81c4a811c2e00be9cd05702bd3ec31725e5 languageName: node linkType: hard From 97f761b1ce20d5a7c087b0187ad992be2433d509 Mon Sep 17 00:00:00 2001 From: 0xmaharishi Date: Mon, 30 May 2022 14:56:26 +0100 Subject: [PATCH 10/20] feat: booster --- convex-platform | 2 +- test-fork/FullDeployment.spec.ts | 6 +++--- yarn.lock | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/convex-platform b/convex-platform index b65977cf..5ccfd178 160000 --- a/convex-platform +++ b/convex-platform @@ -1 +1 @@ -Subproject commit b65977cf01da64d5a5112f379cf54aba98059410 +Subproject commit 5ccfd178b68b85e351a23ff444b4b369b2a2a0d6 diff --git a/test-fork/FullDeployment.spec.ts b/test-fork/FullDeployment.spec.ts index 194a20d2..0e6f7601 100644 --- a/test-fork/FullDeployment.spec.ts +++ b/test-fork/FullDeployment.spec.ts @@ -1717,10 +1717,10 @@ describe("Full Deployment", () => { }); it("allows boosterOwner to call all fns on booster", async () => { const { booster, boosterOwner } = phase4; - await booster.connect(daoSigner.signer).setFeeManager(boosterOwner.address); + await boosterOwner.connect(daoSigner.signer).setFeeManager(config.multisigs.treasuryMultisig); + expect(await booster.feeManager()).eq(config.multisigs.treasuryMultisig); await boosterOwner.connect(daoSigner.signer).setFeeManager(daoSigner.address); - expect(await booster.feeManager()).eq(daoSigner.address); await boosterOwner.connect(daoSigner.signer).setFactories(ZERO_ADDRESS, ZERO_ADDRESS, ZERO_ADDRESS); expect(await booster.stashFactory()).eq(ZERO_ADDRESS); @@ -1730,9 +1730,9 @@ describe("Full Deployment", () => { await boosterOwner.connect(daoSigner.signer).setArbitrator(ZERO_ADDRESS); expect(await booster.rewardArbitrator()).eq(ZERO_ADDRESS); - await booster.connect(daoSigner.signer).setVoteDelegate(boosterOwner.address); await boosterOwner.connect(daoSigner.signer).setVoteDelegate(ZERO_ADDRESS); expect(await booster.voteDelegate()).eq(ZERO_ADDRESS); + await boosterOwner.connect(daoSigner.signer).setVoteDelegate(daoSigner.address); await boosterOwner.connect(daoSigner.signer).updateFeeInfo(config.addresses.token, false); expect((await booster.feeTokens(config.addresses.token)).active).eq(false); diff --git a/yarn.lock b/yarn.lock index 5968d7d7..8d8ae5c9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4560,10 +4560,10 @@ __metadata: "convex-platform@file:convex-platform/contracts::locator=%40aurafinance%2Faura-contracts%40workspace%3A.": version: 0.0.0 - resolution: "convex-platform@file:convex-platform/contracts#convex-platform/contracts::hash=4ac14c&locator=%40aurafinance%2Faura-contracts%40workspace%3A." + resolution: "convex-platform@file:convex-platform/contracts#convex-platform/contracts::hash=d014fe&locator=%40aurafinance%2Faura-contracts%40workspace%3A." dependencies: "@openzeppelin/contracts-0.6": "npm:@openzeppelin/contracts@3.4.0" - checksum: eefe37288431819dbe79b768537d3556b0ac10c0d5501ec0b7dee80e2b0b5f26d167f069c25fdaad94b1b86c9fcca81c4a811c2e00be9cd05702bd3ec31725e5 + checksum: 4e21e1b2e7c79098cf404bd6e3a78898a0d5dd839ebf5828867f82cf9258a8c8399599fb74ec097722557fe88a22cec4741689fc23d569630a8a5eb49fc849db languageName: node linkType: hard From ae2bcf07ac8fdef0f2180e7e3b45153c757d7fe1 Mon Sep 17 00:00:00 2001 From: 0xmaharishi Date: Mon, 30 May 2022 15:13:43 +0100 Subject: [PATCH 11/20] feat: convexMasterChef --- convex-platform | 2 +- scripts/deploySystem.ts | 2 +- test/ConvexMasterChef.spec.ts | 2 +- yarn.lock | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/convex-platform b/convex-platform index 5ccfd178..10a99b73 160000 --- a/convex-platform +++ b/convex-platform @@ -1 +1 @@ -Subproject commit 5ccfd178b68b85e351a23ff444b4b369b2a2a0d6 +Subproject commit 10a99b73a4dd041c03964a83aa49102d79141aa7 diff --git a/scripts/deploySystem.ts b/scripts/deploySystem.ts index 5afd0b8f..81087229 100644 --- a/scripts/deploySystem.ts +++ b/scripts/deploySystem.ts @@ -818,7 +818,7 @@ async function deployPhase2( tx = await cvx.transfer(chef.address, distroList.lpIncentives); await waitForTx(tx, debug, waitForBlocks); - tx = await chef.add(1000, cvxCrvBpt.address, ZERO_ADDRESS, false); + tx = await chef.add(1000, cvxCrvBpt.address, ZERO_ADDRESS); await waitForTx(tx, debug, waitForBlocks); tx = await chef.transferOwnership(multisigs.daoMultisig); diff --git a/test/ConvexMasterChef.spec.ts b/test/ConvexMasterChef.spec.ts index 4bceb5b3..373de2af 100644 --- a/test/ConvexMasterChef.spec.ts +++ b/test/ConvexMasterChef.spec.ts @@ -214,7 +214,7 @@ describe("ConvexMasterChef", () => { await randomToken.transfer(chef.address, simpleToExactAmount(100000)); // Test - await chef.add(1000, randomToken.address, ZERO_ADDRESS, false); + await chef.add(1000, randomToken.address, ZERO_ADDRESS); // Then const poolInfo: PoolInfo = await chef.poolInfo(pidRtkn); expect(await chef.totalAllocPoint(), "totalAllocPoint").to.eq(2000); diff --git a/yarn.lock b/yarn.lock index 8d8ae5c9..386e2ff3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4560,10 +4560,10 @@ __metadata: "convex-platform@file:convex-platform/contracts::locator=%40aurafinance%2Faura-contracts%40workspace%3A.": version: 0.0.0 - resolution: "convex-platform@file:convex-platform/contracts#convex-platform/contracts::hash=d014fe&locator=%40aurafinance%2Faura-contracts%40workspace%3A." + resolution: "convex-platform@file:convex-platform/contracts#convex-platform/contracts::hash=acf2e7&locator=%40aurafinance%2Faura-contracts%40workspace%3A." dependencies: "@openzeppelin/contracts-0.6": "npm:@openzeppelin/contracts@3.4.0" - checksum: 4e21e1b2e7c79098cf404bd6e3a78898a0d5dd839ebf5828867f82cf9258a8c8399599fb74ec097722557fe88a22cec4741689fc23d569630a8a5eb49fc849db + checksum: 980783880d9915eda390029b12331864ffdb6826652a92780e784b9661994f97318e839dda5458260f1776286448369247ee3663eec0260511ec674038cc5912 languageName: node linkType: hard From e75525c9b6bcb310da0753677d9dc4b49a684e6f Mon Sep 17 00:00:00 2001 From: 0xmaharishi Date: Mon, 30 May 2022 15:47:34 +0100 Subject: [PATCH 12/20] feat: crvDepositor and wrapper --- contracts/AuraStakingProxy.sol | 12 +++++++++++- convex-platform | 2 +- test-fork/FullDeployment.spec.ts | 4 ++-- test/AuraLocker.spec.ts | 12 ++++++------ test/AuraStakingProxy.spec.ts | 8 ++++---- test/CrvDepositor.spec.ts | 26 ++------------------------ yarn.lock | 4 ++-- 7 files changed, 28 insertions(+), 40 deletions(-) diff --git a/contracts/AuraStakingProxy.sol b/contracts/AuraStakingProxy.sol index 859d252e..602f4953 100644 --- a/contracts/AuraStakingProxy.sol +++ b/contracts/AuraStakingProxy.sol @@ -152,6 +152,11 @@ contract AuraStakingProxy { IERC20(_token).safeTransfer(_to, bal); } + function distribute(uint256 _minOut) external { + require(msg.sender == keeper, "!auth"); + _distribute(_minOut); + } + /** * @dev Collects cvxCRV rewards from cvxRewardPool, converts any CRV deposited directly from * the booster, and then applies the rewards to the cvxLocker, rewarding the caller in the process. @@ -161,11 +166,16 @@ contract AuraStakingProxy { if (keeper != address(0)) { require(msg.sender == keeper, "!auth"); } + _distribute(0); + } + function _distribute(uint256 _minOut) internal { //convert crv to cvxCrv uint256 crvBal = IERC20(crv).balanceOf(address(this)); if (crvBal > 0) { - uint256 minOut = ICrvDepositorWrapper(crvDepositorWrapper).getMinOut(crvBal, outputBps); + uint256 minOut = _minOut != 0 + ? _minOut + : ICrvDepositorWrapper(crvDepositorWrapper).getMinOut(crvBal, outputBps); ICrvDepositorWrapper(crvDepositorWrapper).deposit(crvBal, minOut, true, address(0)); } diff --git a/convex-platform b/convex-platform index 10a99b73..8f2100d9 160000 --- a/convex-platform +++ b/convex-platform @@ -1 +1 @@ -Subproject commit 10a99b73a4dd041c03964a83aa49102d79141aa7 +Subproject commit 8f2100d9a95b9c979210b1459019e82bcc3d10f3 diff --git a/test-fork/FullDeployment.spec.ts b/test-fork/FullDeployment.spec.ts index 0e6f7601..28c088fb 100644 --- a/test-fork/FullDeployment.spec.ts +++ b/test-fork/FullDeployment.spec.ts @@ -1587,7 +1587,7 @@ describe("Full Deployment", () => { const callerCvxCrvBalanceBefore = await phase4.cvxCrv.balanceOf(stakerAddress); const cvxLockerCvxCrvBalanceBefore = await phase4.cvxCrv.balanceOf(phase4.cvxLocker.address); - await phase4.cvxStakingProxy.connect(staker.signer).distribute(); + await phase4.cvxStakingProxy.connect(staker.signer)["distribute()"](); const callerCvxCrvBalanceAfter = await phase4.cvxCrv.balanceOf(stakerAddress); const cvxLockerCvxCrvBalanceAfter = await phase4.cvxCrv.balanceOf(phase4.cvxLocker.address); @@ -1798,7 +1798,7 @@ describe("Full Deployment", () => { const cvxCrvBalanceBefore = await phase4.cvxCrv.balanceOf(stakerAddress); await getCrv(phase4.booster.address, simpleToExactAmount(1)); await phase4.booster.earmarkRewards(0); - await phase4.cvxStakingProxy.distribute(); + await phase4.cvxStakingProxy["distribute()"](); await increaseTime(ONE_HOUR); const rewards = await phase4.cvxLocker.claimableRewards(stakerAddress); expect(rewards[0].amount).gt(0); diff --git a/test/AuraLocker.spec.ts b/test/AuraLocker.spec.ts index 4dc62165..ca145ec2 100644 --- a/test/AuraLocker.spec.ts +++ b/test/AuraLocker.spec.ts @@ -270,7 +270,7 @@ describe("AuraLocker", () => { expect(stakingCrvBalance).to.equal(rate.mul(incentive).div(10000)); - const tx = await cvxStakingProxy.distribute(); + const tx = await cvxStakingProxy["distribute()"](); const receipt = await tx.wait(); const event = receipt.events.find(e => e.event === "RewardsDistributed"); @@ -393,7 +393,7 @@ describe("AuraLocker", () => { expect(stakingCrvBalance).to.equal(rate.mul(incentive).div(10000)); const balBefore = await cvxCrv.balanceOf(auraLocker.address); - const tx = await cvxStakingProxy.distribute(); + const tx = await cvxStakingProxy["distribute()"](); await tx.wait(); const balAfter = await cvxCrv.balanceOf(auraLocker.address); @@ -951,7 +951,7 @@ describe("AuraLocker", () => { expect(stakingCrvBalance).to.equal(rate.mul(incentive).div(10000)); - await expect(cvxStakingProxy.distribute()).to.be.revertedWith("!balance"); + await expect(cvxStakingProxy["distribute()"]()).to.be.revertedWith("!balance"); }); it("distribute rewards from the booster", async () => { await auraLocker.connect(alice).lock(aliceAddress, simpleToExactAmount(100)); @@ -959,7 +959,7 @@ describe("AuraLocker", () => { }); it("queues rewards when cvxCrv period is finished", async () => { await auraLocker.connect(alice).lock(aliceAddress, simpleToExactAmount(100)); - // AuraStakingProxy.distribute(), faked by impersonating account + // AuraStakingProxy["distribute()"](), faked by impersonating account let rewards = simpleToExactAmount(100); const rewardDistribution = await auraLocker.rewardsDuration(); const cvxCrvLockerBalance0 = await cvxCrv.balanceOf(auraLocker.address); @@ -1000,7 +1000,7 @@ describe("AuraLocker", () => { expect(timeStamp, "reward period finish").to.gte(rewardData0.periodFinish); expect(await cvxCrv.balanceOf(cvxStakingProxyAccount.address)).to.gt(0); - // cvxStakingProxy.distribute();=>auraLocker.queueNewRewards() + // cvxStakingProxy["distribute()"]();=>auraLocker.queueNewRewards() // First distribution to update the reward finish period. let rewards = await distributeRewardsFromBooster(); // Validate @@ -1140,7 +1140,7 @@ describe("AuraLocker", () => { tx = await booster.earmarkRewards(boosterPoolId); await tx.wait(); - tx = await cvxStakingProxy.distribute(); + tx = await cvxStakingProxy["distribute()"](); await tx.wait(); const lock = await auraLocker.userLocks(aliceAddress, 0); diff --git a/test/AuraStakingProxy.spec.ts b/test/AuraStakingProxy.spec.ts index aff2d68c..d069b2bd 100644 --- a/test/AuraStakingProxy.spec.ts +++ b/test/AuraStakingProxy.spec.ts @@ -246,12 +246,12 @@ describe("AuraStakingProxy", () => { it("fails to distribute if caller is not the keeper", async () => { const keeper = await accounts[1].getAddress(); await contracts.cvxStakingProxy.setKeeper(keeper); - await expect(contracts.cvxStakingProxy.connect(accounts[0]).distribute()).to.be.revertedWith("!auth"); - await contracts.cvxStakingProxy.connect(accounts[1]).distribute(); + await expect(contracts.cvxStakingProxy.connect(accounts[0])["distribute()"]()).to.be.revertedWith("!auth"); + await contracts.cvxStakingProxy.connect(accounts[1])["distribute()"](); }); it("allows anyone to distribute", async () => { await contracts.cvxStakingProxy.setKeeper(ZERO_ADDRESS); - await contracts.cvxStakingProxy.connect(accounts[0]).distribute(); + await contracts.cvxStakingProxy.connect(accounts[0])["distribute()"](); }); it("distribute rewards from the booster", async () => { await contracts.booster.earmarkRewards(0); @@ -263,7 +263,7 @@ describe("AuraStakingProxy", () => { expect(stakingProxyBalance).to.equal(rate.mul(incentive).div(10000)); const balanceBefore = await contracts.cvxCrv.balanceOf(contracts.cvxLocker.address); - const tx = await contracts.cvxStakingProxy.distribute(); + const tx = await contracts.cvxStakingProxy["distribute()"](); await tx.wait(); const balanceAfter = await contracts.cvxCrv.balanceOf(contracts.cvxLocker.address); diff --git a/test/CrvDepositor.spec.ts b/test/CrvDepositor.spec.ts index f0da0bd1..e047d521 100644 --- a/test/CrvDepositor.spec.ts +++ b/test/CrvDepositor.spec.ts @@ -185,30 +185,8 @@ describe("CrvDepositor", () => { }); it("deposit skips lock", async () => { - const lock = true; - const stakeAddress = "0x0000000000000000000000000000000000000000"; - const crvBalance = await mocks.crvBpt.balanceOf(aliceAddress); - const amount = crvBalance.mul(10).div(100); - - const beforeLockTime = await mocks.votingEscrow.lockTimes(voterProxy.address); - const beforeLockAmount = await mocks.votingEscrow.lockAmounts(voterProxy.address); - const cvxCrvBalanceBefore = await cvxCrv.balanceOf(aliceAddress); - - const tx = await crvDepositor["deposit(uint256,bool,address)"](amount, lock, stakeAddress); - await tx.wait(); - - const cvxCrvBalanceAfter = await cvxCrv.balanceOf(aliceAddress); - const cvxCrvBalanceDelta = cvxCrvBalanceAfter.sub(cvxCrvBalanceBefore); - expect(cvxCrvBalanceDelta).to.equal(amount); - - const afterLockTime = await mocks.votingEscrow.lockTimes(voterProxy.address); - const afterLockAmount = await mocks.votingEscrow.lockAmounts(voterProxy.address); - - const lockTimeDelta = afterLockTime.sub(beforeLockTime); - const lockAmountDelta = afterLockAmount.sub(beforeLockAmount); - - expect(lockTimeDelta.toString()).to.equal("0"); - expect(lockAmountDelta.toString()).to.equal("0"); + const tx = crvDepositor["deposit(uint256,bool,address)"](simpleToExactAmount(1), true, ZERO_ADDRESS); + await expect(tx).to.revertedWith("cooldown"); }); }); describe("setting setters", () => { diff --git a/yarn.lock b/yarn.lock index 386e2ff3..32a57e65 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4560,10 +4560,10 @@ __metadata: "convex-platform@file:convex-platform/contracts::locator=%40aurafinance%2Faura-contracts%40workspace%3A.": version: 0.0.0 - resolution: "convex-platform@file:convex-platform/contracts#convex-platform/contracts::hash=acf2e7&locator=%40aurafinance%2Faura-contracts%40workspace%3A." + resolution: "convex-platform@file:convex-platform/contracts#convex-platform/contracts::hash=bcca87&locator=%40aurafinance%2Faura-contracts%40workspace%3A." dependencies: "@openzeppelin/contracts-0.6": "npm:@openzeppelin/contracts@3.4.0" - checksum: 980783880d9915eda390029b12331864ffdb6826652a92780e784b9661994f97318e839dda5458260f1776286448369247ee3663eec0260511ec674038cc5912 + checksum: b3aeed1b56fc8fbda0b5317d5bc76af034c819ac2f0064494e9e14a77027118b5fca60aaa3be3093859e960a7625ca38c393a8ac76504893dff5802d077a22b1 languageName: node linkType: hard From 9c2d215d06ae577143463132fffbb25be8a8cf49 Mon Sep 17 00:00:00 2001 From: 0xmaharishi Date: Mon, 30 May 2022 16:15:04 +0100 Subject: [PATCH 13/20] feat: extraRewardsDistributor --- contracts/ExtraRewardsDistributor.sol | 29 ++++++++++++++++++--------- scripts/deploySystem.ts | 6 ++++++ test/ExtraRewardsDistributor.spec.ts | 11 +++++----- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/contracts/ExtraRewardsDistributor.sol b/contracts/ExtraRewardsDistributor.sol index e029858a..721e5d6d 100644 --- a/contracts/ExtraRewardsDistributor.sol +++ b/contracts/ExtraRewardsDistributor.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.11; import { IExtraRewardsDistributor, IAuraLocker } from "./Interfaces.sol"; import { IERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/utils/SafeERC20.sol"; +import { Ownable } from "@openzeppelin/contracts-0.8/access/Ownable.sol"; import { ReentrancyGuard } from "@openzeppelin/contracts-0.8/security/ReentrancyGuard.sol"; /** @@ -11,11 +12,13 @@ import { ReentrancyGuard } from "@openzeppelin/contracts-0.8/security/Reentrancy * @author adapted from ConvexFinance * @notice Allows anyone to distribute rewards to the AuraLocker at a given epoch. */ -contract ExtraRewardsDistributor is ReentrancyGuard, IExtraRewardsDistributor { +contract ExtraRewardsDistributor is ReentrancyGuard, IExtraRewardsDistributor, Ownable { using SafeERC20 for IERC20; IAuraLocker public immutable auraLocker; + // user -> canAdd + mapping(address => bool) public canAddReward; // token -> epoch -> amount mapping(address => mapping(uint256 => uint256)) public rewardData; // token -> epochList @@ -25,6 +28,7 @@ contract ExtraRewardsDistributor is ReentrancyGuard, IExtraRewardsDistributor { /* ========== EVENTS ========== */ + event WhitelistModified(address user, bool canAdd); event RewardAdded(address indexed token, uint256 indexed epoch, uint256 reward); event RewardPaid(address indexed user, address indexed token, uint256 reward, uint256 index); event RewardForfeited(address indexed user, address indexed token, uint256 index); @@ -33,10 +37,17 @@ contract ExtraRewardsDistributor is ReentrancyGuard, IExtraRewardsDistributor { * @dev Simple constructoor * @param _auraLocker Aura Locker address */ - constructor(address _auraLocker) { + constructor(address _auraLocker) Ownable() { auraLocker = IAuraLocker(_auraLocker); } + /* ========== ADD WHITELIST ========== */ + + function modifyWhitelist(address _depositor, bool _canAdd) external onlyOwner { + canAddReward[_depositor] = _canAdd; + emit WhitelistModified(_depositor, _canAdd); + } + /* ========== ADD REWARDS ========== */ /** @@ -89,6 +100,9 @@ contract ExtraRewardsDistributor is ReentrancyGuard, IExtraRewardsDistributor { uint256 _amount, uint256 _epoch ) internal nonReentrant { + require(canAddReward[msg.sender], "!auth"); + require(_amount > 0, "!amount"); + // Pull before reward accrual IERC20(_token).safeTransferFrom(msg.sender, address(this), _amount); @@ -120,16 +134,11 @@ contract ExtraRewardsDistributor is ReentrancyGuard, IExtraRewardsDistributor { /** * @notice Claim rewards for a specific token at a specific epoch - * @param _account Address of vlCVX holder * @param _token Reward token address * @param _startIndex Index of rewardEpochs[_token] to start checking for rewards from */ - function getReward( - address _account, - address _token, - uint256 _startIndex - ) public { - _getReward(_account, _token, _startIndex); + function getReward(address _token, uint256 _startIndex) public { + _getReward(msg.sender, _token, _startIndex); } /** @@ -142,7 +151,7 @@ contract ExtraRewardsDistributor is ReentrancyGuard, IExtraRewardsDistributor { address _account, address _token, uint256 _startIndex - ) public { + ) internal nonReentrant { //get claimable tokens (uint256 claimableTokens, uint256 index) = _allClaimableRewards(_account, _token, _startIndex); diff --git a/scripts/deploySystem.ts b/scripts/deploySystem.ts index 81087229..e7391134 100644 --- a/scripts/deploySystem.ts +++ b/scripts/deploySystem.ts @@ -646,6 +646,12 @@ async function deployPhase2( tx = await booster.setOwner(boosterOwner.address); await waitForTx(tx, debug, waitForBlocks); + tx = await extraRewardsDistributor.modifyWhitelist(penaltyForwarder.address, true); + await waitForTx(tx, debug, waitForBlocks); + + tx = await extraRewardsDistributor.transferOwnership(multisigs.daoMultisig); + await waitForTx(tx, debug, waitForBlocks); + // ----------------------------- // 2.2. Token liquidity: // - Schedule: vesting (team, treasury, etc) diff --git a/test/ExtraRewardsDistributor.spec.ts b/test/ExtraRewardsDistributor.spec.ts index 045b1416..171bba56 100644 --- a/test/ExtraRewardsDistributor.spec.ts +++ b/test/ExtraRewardsDistributor.spec.ts @@ -66,6 +66,9 @@ describe("ExtraRewardsDistributor", () => { mockErc20 = await new MockERC20__factory(alice).deploy("MockERC20", "mk20", 18, aliceAddress, 1000); await mockErc20.connect(alice).transfer(bobAddress, simpleToExactAmount(200)); mockErc20X = await new MockERC20__factory(alice).deploy("MockERC20 X", "MKTX", 18, aliceAddress, 1000); + + await contracts.extraRewardsDistributor.modifyWhitelist(aliceAddress, true); + await contracts.extraRewardsDistributor.modifyWhitelist(bobAddress, true); }); async function verifyAddRewards(sender: Signer, fundAmount: BN, epoch: number) { @@ -333,9 +336,7 @@ describe("ExtraRewardsDistributor", () => { expect(claimableRewardsAtLatestEpoch, "bob claimable rewards at given epoch").to.gt(0); expect(await distributor.userClaims(mockErc20.address, bobAddress), "user claims").to.eq(0); // When - const tx = distributor - .connect(bob) - ["getReward(address,address,uint256)"](bobAddress, mockErc20.address, epochIndex); + const tx = distributor.connect(bob)["getReward(address,uint256)"](mockErc20.address, epochIndex); // Then await expect(tx) @@ -364,9 +365,7 @@ describe("ExtraRewardsDistributor", () => { epoch - 1, ); // When - const tx = distributor - .connect(bob) - ["getReward(address,address,uint256)"](bobAddress, mockErc20.address, epoch); + const tx = distributor.connect(bob)["getReward(address,uint256)"](mockErc20.address, epoch); // Then await expect(tx).not.to.emit(distributor, "RewardPaid"); expect(await mockErc20.balanceOf(bobAddress), "bob balance").to.eq(bobBalanceBefore); From 3d036ce6324dc872da8455749f610ee7c4e39bfa Mon Sep 17 00:00:00 2001 From: 0xmaharishi Date: Mon, 30 May 2022 16:22:33 +0100 Subject: [PATCH 14/20] feat: compiler versions --- contracts/Aura.sol | 2 +- contracts/AuraBalRewardPool.sol | 2 +- contracts/AuraClaimZap.sol | 2 +- contracts/AuraLocker.sol | 2 +- contracts/AuraMath.sol | 2 +- contracts/AuraMerkleDrop.sol | 2 +- contracts/AuraMinter.sol | 2 +- contracts/AuraPenaltyForwarder.sol | 2 +- contracts/AuraStakingProxy.sol | 2 +- contracts/AuraVestedEscrow.sol | 2 +- contracts/BalLiquidityProvider.sol | 2 +- contracts/ClaimFeesHelper.sol | 2 +- contracts/ExtraRewardsDistributor.sol | 2 +- contracts/Interfaces.sol | 1 - contracts/RewardPoolDepositWrapper.sol | 2 +- contracts/mocks/MockAuraLockor.sol | 2 +- contracts/mocks/MockAuraMath.sol | 2 +- convex-platform | 2 +- yarn.lock | 4 ++-- 19 files changed, 19 insertions(+), 20 deletions(-) diff --git a/contracts/Aura.sol b/contracts/Aura.sol index 21ea7d15..5bbf58ae 100644 --- a/contracts/Aura.sol +++ b/contracts/Aura.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.11; +pragma solidity 0.8.11; import { ERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/ERC20.sol"; import { AuraMath } from "./AuraMath.sol"; diff --git a/contracts/AuraBalRewardPool.sol b/contracts/AuraBalRewardPool.sol index 76249544..6145a819 100644 --- a/contracts/AuraBalRewardPool.sol +++ b/contracts/AuraBalRewardPool.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.11; +pragma solidity 0.8.11; import { AuraMath } from "./AuraMath.sol"; import { IERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/IERC20.sol"; diff --git a/contracts/AuraClaimZap.sol b/contracts/AuraClaimZap.sol index 0a4b375c..d0919ad3 100644 --- a/contracts/AuraClaimZap.sol +++ b/contracts/AuraClaimZap.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.11; +pragma solidity 0.8.11; import { AuraMath } from "./AuraMath.sol"; import { IAuraLocker, ICrvDepositorWrapper } from "./Interfaces.sol"; diff --git a/contracts/AuraLocker.sol b/contracts/AuraLocker.sol index 295bcbed..685f0a0a 100644 --- a/contracts/AuraLocker.sol +++ b/contracts/AuraLocker.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.11; +pragma solidity 0.8.11; import { IERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/utils/SafeERC20.sol"; diff --git a/contracts/AuraMath.sol b/contracts/AuraMath.sol index 36678c91..5aa70afc 100644 --- a/contracts/AuraMath.sol +++ b/contracts/AuraMath.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.11; +pragma solidity 0.8.11; /// @notice A library for performing overflow-/underflow-safe math, /// updated with awesomeness from of DappHub (https://github.com/dapphub/ds-math). diff --git a/contracts/AuraMerkleDrop.sol b/contracts/AuraMerkleDrop.sol index 64d2a5fc..9ebb0eaf 100644 --- a/contracts/AuraMerkleDrop.sol +++ b/contracts/AuraMerkleDrop.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.11; +pragma solidity 0.8.11; import { MerkleProof } from "@openzeppelin/contracts-0.8/utils/cryptography/MerkleProof.sol"; import { IAuraLocker } from "./Interfaces.sol"; diff --git a/contracts/AuraMinter.sol b/contracts/AuraMinter.sol index 521077d4..6ecb3ef0 100644 --- a/contracts/AuraMinter.sol +++ b/contracts/AuraMinter.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.11; +pragma solidity 0.8.11; import { Ownable } from "@openzeppelin/contracts-0.8/access/Ownable.sol"; import { AuraToken } from "./Aura.sol"; diff --git a/contracts/AuraPenaltyForwarder.sol b/contracts/AuraPenaltyForwarder.sol index 614a3dd4..7f398b5a 100644 --- a/contracts/AuraPenaltyForwarder.sol +++ b/contracts/AuraPenaltyForwarder.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.11; +pragma solidity 0.8.11; import { IExtraRewardsDistributor } from "./Interfaces.sol"; import { IERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/IERC20.sol"; diff --git a/contracts/AuraStakingProxy.sol b/contracts/AuraStakingProxy.sol index 602f4953..04f814a0 100644 --- a/contracts/AuraStakingProxy.sol +++ b/contracts/AuraStakingProxy.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.11; +pragma solidity 0.8.11; import { Address } from "@openzeppelin/contracts-0.8/utils/Address.sol"; import { IERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/IERC20.sol"; diff --git a/contracts/AuraVestedEscrow.sol b/contracts/AuraVestedEscrow.sol index 70989969..ff60cbc6 100644 --- a/contracts/AuraVestedEscrow.sol +++ b/contracts/AuraVestedEscrow.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.11; +pragma solidity 0.8.11; import { IAuraLocker } from "./Interfaces.sol"; import { IERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/IERC20.sol"; diff --git a/contracts/BalLiquidityProvider.sol b/contracts/BalLiquidityProvider.sol index a952ad16..593df86f 100644 --- a/contracts/BalLiquidityProvider.sol +++ b/contracts/BalLiquidityProvider.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.11; +pragma solidity 0.8.11; import { IVault } from "./Interfaces.sol"; import { IERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/IERC20.sol"; diff --git a/contracts/ClaimFeesHelper.sol b/contracts/ClaimFeesHelper.sol index 408c2eca..5ae2ceb5 100644 --- a/contracts/ClaimFeesHelper.sol +++ b/contracts/ClaimFeesHelper.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.11; +pragma solidity 0.8.11; import { IERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/IERC20.sol"; import { IFeeDistributor } from "./mocks/balancer/MockFeeDistro.sol"; diff --git a/contracts/ExtraRewardsDistributor.sol b/contracts/ExtraRewardsDistributor.sol index 721e5d6d..0c756acf 100644 --- a/contracts/ExtraRewardsDistributor.sol +++ b/contracts/ExtraRewardsDistributor.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.11; +pragma solidity 0.8.11; import { IExtraRewardsDistributor, IAuraLocker } from "./Interfaces.sol"; import { IERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/IERC20.sol"; diff --git a/contracts/Interfaces.sol b/contracts/Interfaces.sol index bdda9938..aed63efd 100644 --- a/contracts/Interfaces.sol +++ b/contracts/Interfaces.sol @@ -1,6 +1,5 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.11; -pragma abicoder v2; interface IPriceOracle { struct OracleAverageQuery { diff --git a/contracts/RewardPoolDepositWrapper.sol b/contracts/RewardPoolDepositWrapper.sol index 9c5cd192..24e76950 100644 --- a/contracts/RewardPoolDepositWrapper.sol +++ b/contracts/RewardPoolDepositWrapper.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.11; +pragma solidity 0.8.11; import { IVault } from "./Interfaces.sol"; import { IERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/IERC20.sol"; diff --git a/contracts/mocks/MockAuraLockor.sol b/contracts/mocks/MockAuraLockor.sol index 75f99904..59d97a6a 100644 --- a/contracts/mocks/MockAuraLockor.sol +++ b/contracts/mocks/MockAuraLockor.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.11; +pragma solidity 0.8.11; import { IERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/utils/SafeERC20.sol"; diff --git a/contracts/mocks/MockAuraMath.sol b/contracts/mocks/MockAuraMath.sol index f13821b4..29dfed73 100644 --- a/contracts/mocks/MockAuraMath.sol +++ b/contracts/mocks/MockAuraMath.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.11; +pragma solidity 0.8.11; import { AuraMath, AuraMath32, AuraMath112, AuraMath224 } from "../AuraMath.sol"; diff --git a/convex-platform b/convex-platform index 8f2100d9..67a373fa 160000 --- a/convex-platform +++ b/convex-platform @@ -1 +1 @@ -Subproject commit 8f2100d9a95b9c979210b1459019e82bcc3d10f3 +Subproject commit 67a373fa7bb159360427bf040518e3baceb3a59f diff --git a/yarn.lock b/yarn.lock index 32a57e65..235466e7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4560,10 +4560,10 @@ __metadata: "convex-platform@file:convex-platform/contracts::locator=%40aurafinance%2Faura-contracts%40workspace%3A.": version: 0.0.0 - resolution: "convex-platform@file:convex-platform/contracts#convex-platform/contracts::hash=bcca87&locator=%40aurafinance%2Faura-contracts%40workspace%3A." + resolution: "convex-platform@file:convex-platform/contracts#convex-platform/contracts::hash=18fe67&locator=%40aurafinance%2Faura-contracts%40workspace%3A." dependencies: "@openzeppelin/contracts-0.6": "npm:@openzeppelin/contracts@3.4.0" - checksum: b3aeed1b56fc8fbda0b5317d5bc76af034c819ac2f0064494e9e14a77027118b5fca60aaa3be3093859e960a7625ca38c393a8ac76504893dff5802d077a22b1 + checksum: a20e8d270626be8c25b815f3e325d517151e05b06f5d3a90417912aeeff9dc0e3d704f53167078a13cb78683ce9c9626ac043b2d95b02f4414f81b27ee26120f languageName: node linkType: hard From b6b5375614f114e44038884391e283910f959a1f Mon Sep 17 00:00:00 2001 From: 0xmaharishi Date: Mon, 30 May 2022 16:28:52 +0100 Subject: [PATCH 15/20] feat: penaltyForwarder --- contracts/AuraPenaltyForwarder.sol | 23 ++++++++++++++++++----- scripts/deploySystem.ts | 2 +- test-fork/FullDeployment.spec.ts | 6 ++++-- test/AuraPenaltyForwarder.spec.ts | 4 +--- 4 files changed, 24 insertions(+), 11 deletions(-) diff --git a/contracts/AuraPenaltyForwarder.sol b/contracts/AuraPenaltyForwarder.sol index 7f398b5a..42a5a985 100644 --- a/contracts/AuraPenaltyForwarder.sol +++ b/contracts/AuraPenaltyForwarder.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.11; import { IExtraRewardsDistributor } from "./Interfaces.sol"; +import { Ownable } from "@openzeppelin/contracts-0.8/access/Ownable.sol"; import { IERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/utils/SafeERC20.sol"; @@ -9,35 +10,38 @@ import { SafeERC20 } from "@openzeppelin/contracts-0.8/token/ERC20/utils/SafeERC * @title AuraPenaltyForwarder * @dev Receives a given token and forwards it on to a distribution contract. */ -contract AuraPenaltyForwarder { +contract AuraPenaltyForwarder is Ownable { using SafeERC20 for IERC20; - IExtraRewardsDistributor public immutable distributor; + IExtraRewardsDistributor public distributor; IERC20 public immutable token; uint256 public immutable distributionDelay; uint256 public lastDistribution; event Forwarded(uint256 amount); + event DistributorChanged(address newDistributor); /** * @dev During deployment approves the distributor to spend all tokens * @param _distributor Contract that will distribute tokens * @param _token Token to be distributed * @param _delay Delay between each distribution trigger + * @param _dao Address of DAO */ constructor( address _distributor, address _token, - uint256 _delay - ) { + uint256 _delay, + address _dao + ) Ownable() { distributor = IExtraRewardsDistributor(_distributor); token = IERC20(_token); distributionDelay = _delay; lastDistribution = block.timestamp; - token.safeApprove(address(distributor), type(uint256).max); + _transferOwnership(_dao); } /** @@ -50,8 +54,17 @@ contract AuraPenaltyForwarder { uint256 bal = token.balanceOf(address(this)); require(bal > 0, "!empty"); + token.safeIncreaseAllowance(address(distributor), bal); distributor.addReward(address(token), bal); emit Forwarded(bal); } + + /** + * @dev Updates distributor address + */ + function setDistributor(address _distributor) public onlyOwner { + distributor = IExtraRewardsDistributor(_distributor); + emit DistributorChanged(_distributor); + } } diff --git a/scripts/deploySystem.ts b/scripts/deploySystem.ts index e7391134..fc6c4988 100644 --- a/scripts/deploySystem.ts +++ b/scripts/deploySystem.ts @@ -546,7 +546,7 @@ async function deployPhase2( hre, new AuraPenaltyForwarder__factory(deployer), "AuraPenaltyForwarder", - [extraRewardsDistributor.address, cvx.address, ONE_WEEK.mul(7).div(2)], + [extraRewardsDistributor.address, cvx.address, ONE_WEEK.mul(7).div(2), multisigs.daoMultisig], {}, debug, waitForBlocks, diff --git a/test-fork/FullDeployment.spec.ts b/test-fork/FullDeployment.spec.ts index 28c088fb..d69dfd66 100644 --- a/test-fork/FullDeployment.spec.ts +++ b/test-fork/FullDeployment.spec.ts @@ -637,12 +637,14 @@ describe("Full Deployment", () => { expect(await penaltyForwarder.distributor()).eq(extraRewardsDistributor.address); expect(await penaltyForwarder.token()).eq(cvx.address); + expect(await penaltyForwarder.owner()).eq(config.multisigs.daoMultisig); expect(await penaltyForwarder.distributionDelay()).eq(ONE_WEEK.mul(7).div(2)); assertBNClose(await penaltyForwarder.lastDistribution(), await getTimestamp(), 100); }); it("extraRewardsDistributor has correct config", async () => { const { extraRewardsDistributor, cvxLocker } = phase2; expect(await extraRewardsDistributor.auraLocker()).eq(cvxLocker.address); + expect(await extraRewardsDistributor.owner()).eq(config.multisigs.daoMultisig); }); }); }); @@ -1828,7 +1830,7 @@ describe("Full Deployment", () => { // Check that a penalty has been accrued const penaltyBal = await cvx.balanceOf(penaltyForwarder.address); expect(penaltyBal).gt(0); - const distirbutorBalBefore = await cvx.balanceOf(extraRewardsDistributor.address); + const distributorBalBefore = await cvx.balanceOf(extraRewardsDistributor.address); // Forward the penalty to rewardsDistributor await penaltyForwarder.forward(); @@ -1836,7 +1838,7 @@ describe("Full Deployment", () => { // Check the reward has been added expect(await cvx.balanceOf(penaltyForwarder.address)).eq(0); const distributorBalAfter = await cvx.balanceOf(extraRewardsDistributor.address); - expect(distributorBalAfter.sub(distirbutorBalBefore)).eq(penaltyBal); + expect(distributorBalAfter.sub(distributorBalBefore)).eq(penaltyBal); expect(await extraRewardsDistributor.rewardEpochsCount(cvx.address)).eq(1); // Check the reward is claimable diff --git a/test/AuraPenaltyForwarder.spec.ts b/test/AuraPenaltyForwarder.spec.ts index 7edd9901..1e58b0ff 100644 --- a/test/AuraPenaltyForwarder.spec.ts +++ b/test/AuraPenaltyForwarder.spec.ts @@ -64,9 +64,7 @@ describe("AuraPenaltyForwarder", () => { expect(await penaltyForwarder.lastDistribution(), "lastDistribution").to.lte(currentTime); }); it("forwarder cvx allowance is correct", async () => { - expect(await cvx.allowance(penaltyForwarder.address, distributor.address), "allowance").to.eq( - ethers.constants.MaxUint256, - ); + expect(await cvx.allowance(penaltyForwarder.address, distributor.address), "allowance").to.eq(0); }); describe("forward", async () => { it("fails if the distribution delay is not completed", async () => { From a8be006ff687672f771da26733f31fc1087b34c6 Mon Sep 17 00:00:00 2001 From: 0xmaharishi Date: Mon, 30 May 2022 16:29:53 +0100 Subject: [PATCH 16/20] feat: stashFactoryV2 --- convex-platform | 2 +- yarn.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/convex-platform b/convex-platform index 67a373fa..953a658c 160000 --- a/convex-platform +++ b/convex-platform @@ -1 +1 @@ -Subproject commit 67a373fa7bb159360427bf040518e3baceb3a59f +Subproject commit 953a658c2f5edf559de26deb85d7a7871c0fea14 diff --git a/yarn.lock b/yarn.lock index 235466e7..e4383aed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4560,10 +4560,10 @@ __metadata: "convex-platform@file:convex-platform/contracts::locator=%40aurafinance%2Faura-contracts%40workspace%3A.": version: 0.0.0 - resolution: "convex-platform@file:convex-platform/contracts#convex-platform/contracts::hash=18fe67&locator=%40aurafinance%2Faura-contracts%40workspace%3A." + resolution: "convex-platform@file:convex-platform/contracts#convex-platform/contracts::hash=ebe5f0&locator=%40aurafinance%2Faura-contracts%40workspace%3A." dependencies: "@openzeppelin/contracts-0.6": "npm:@openzeppelin/contracts@3.4.0" - checksum: a20e8d270626be8c25b815f3e325d517151e05b06f5d3a90417912aeeff9dc0e3d704f53167078a13cb78683ce9c9626ac043b2d95b02f4414f81b27ee26120f + checksum: 6755f66be144b36aafb00a2d57cf01a8b4cd017b677c0c7406a0524d9706e3d0ffdbe73eabfdfb860e4f867e484122d2d7fb149201e85ce0702fc8cca04d98ee languageName: node linkType: hard From 90576ee66e08865c40a8770eb8e37169d9eb904e Mon Sep 17 00:00:00 2001 From: 0xmaharishi Date: Mon, 30 May 2022 16:39:50 +0100 Subject: [PATCH 17/20] fix: failing tests --- test-fork/RewardPoolDepositWrapper.spec.ts | 14 ++++++++++++++ test/VoterProxy.spec.ts | 1 + 2 files changed, 15 insertions(+) diff --git a/test-fork/RewardPoolDepositWrapper.spec.ts b/test-fork/RewardPoolDepositWrapper.spec.ts index 11cd63e3..2f865955 100644 --- a/test-fork/RewardPoolDepositWrapper.spec.ts +++ b/test-fork/RewardPoolDepositWrapper.spec.ts @@ -36,6 +36,7 @@ describe("RewardPoolDepositWrapper", () => { let usdc: ERC20; before(async () => { + console.log("i"); await network.provider.request({ method: "hardhat_reset", params: [ @@ -47,11 +48,15 @@ describe("RewardPoolDepositWrapper", () => { }, ], }); + console.log("ii"); deployerAddress = "0xA28ea848801da877E1844F954FF388e857d405e5"; await setupBalances(); + console.log("iii"); deployer = await impersonate(deployerAddress); + console.log("iv"); phase1 = await config.getPhase1(deployer); + console.log("v"); phase2 = await deployPhase2( hre, deployer, @@ -62,18 +67,27 @@ describe("RewardPoolDepositWrapper", () => { config.addresses, debug, ); + console.log("vi"); await getWeth(phase2.balLiquidityProvider.address, simpleToExactAmount(500)); + console.log("vii"); phase3 = await deployPhase3(hre, deployer, phase2, config.multisigs, config.addresses, debug); + console.log("viii"); const daoMultisig = await impersonateAccount(config.multisigs.daoMultisig); + console.log("iix"); const tx = await phase3.poolManager.connect(daoMultisig.signer).setProtectPool(false); + console.log("ix"); await waitForTx(tx, debug); + console.log("x"); phase4 = await deployPhase4(hre, deployer, phase3, config.addresses, debug); + console.log("xi"); staker = await impersonate(stakerAddress); + console.log("xii"); usdc = ERC20__factory.connect(usdcAddress, staker); + console.log("xiii"); await getUSDC(stakerAddress, simpleToExactAmount(100, 6)); }); diff --git a/test/VoterProxy.spec.ts b/test/VoterProxy.spec.ts index 0dbc9a53..30eb8ea2 100644 --- a/test/VoterProxy.spec.ts +++ b/test/VoterProxy.spec.ts @@ -222,6 +222,7 @@ describe("VoterProxy", () => { await auraLocker.lock(deployerAddress, cvxAmount); await increaseTime(86400 * 7); + await extraRewardsDistributor.connect(daoMultisig).modifyWhitelist(voterProxy.address, true); await voterProxy.connect(daoMultisig)["withdraw(address)"](randomToken.address); const rewardDepositBalance = await randomToken.balanceOf(extraRewardsDistributor.address); expect(balance).eq(rewardDepositBalance); From 680fe738b833bf013af3f23ebe7a55eba8dcf3fd Mon Sep 17 00:00:00 2001 From: 0xahtle7 <100377534+0xahtle7@users.noreply.github.com> Date: Mon, 30 May 2022 21:32:33 +0200 Subject: [PATCH 18/20] fix: fork test FullDeployment.spec (#85) * test: increase test coverage * test: fix max limit tx on fork tests --- contracts/mocks/balancer/MockFeeDistro.sol | 2 +- contracts/mocks/curve/MockCurveGauge.sol | 7 ++++-- contracts/mocks/curve/MockVoting.sol | 14 +++++++++--- hardhat-fork.config.ts | 3 +++ tasks/utils/deploy-utils.ts | 2 +- test-fork/FullDeployment.spec.ts | 3 +++ test-fork/RewardPoolDepositWrapper.spec.ts | 17 +++----------- test/AuraPenaltyForwarder.spec.ts | 26 +++++++++++++++++++++- test/ExtraRewardsDistributor.spec.ts | 11 +++++++++ 9 files changed, 63 insertions(+), 22 deletions(-) diff --git a/contracts/mocks/balancer/MockFeeDistro.sol b/contracts/mocks/balancer/MockFeeDistro.sol index 413d1b31..faa68b08 100644 --- a/contracts/mocks/balancer/MockFeeDistro.sol +++ b/contracts/mocks/balancer/MockFeeDistro.sol @@ -43,7 +43,7 @@ contract MockFeeDistributor is IFeeDistributor { function getTokenTimeCursor( IERC20 /* token */ - ) external view returns (uint256) { + ) external pure returns (uint256) { return 1; } } diff --git a/contracts/mocks/curve/MockCurveGauge.sol b/contracts/mocks/curve/MockCurveGauge.sol index b1a41ca7..0013d83f 100644 --- a/contracts/mocks/curve/MockCurveGauge.sol +++ b/contracts/mocks/curve/MockCurveGauge.sol @@ -38,11 +38,14 @@ contract MockCurveGauge is ERC20 { } } - function claimable_reward(address, address) external view returns (uint256) { + function claimable_reward(address, address) external pure returns (uint256) { return 0; } function deposit_reward_token(address, uint256) external {} - function add_reward(address _reward_token, address _distributor) external {} + function add_reward( + address, /* _reward_token */ + address /* _distributor */ + ) external {} } diff --git a/contracts/mocks/curve/MockVoting.sol b/contracts/mocks/curve/MockVoting.sol index 32c37e24..4d5f5d55 100644 --- a/contracts/mocks/curve/MockVoting.sol +++ b/contracts/mocks/curve/MockVoting.sol @@ -34,17 +34,25 @@ contract MockVoting { return gaugeWeights[gauge]; } - function vote_user_slopes(address user, address gauge) external view returns (VotedSlope memory) { + function vote_user_slopes( + address, /*user*/ + address /*gauge*/ + ) external pure returns (VotedSlope memory) { return VotedSlope(0, 0, 0); } // Total vote power used by user - function vote_user_power(address user) external view returns (uint256) { + function vote_user_power( + address /* user */ + ) external pure returns (uint256) { return 0; } // Last user vote's timestamp for each gauge address - function last_user_vote(address user, address gauge) external view returns (uint256) { + function last_user_vote( + address, /*user*/ + address /*gauge*/ + ) external pure returns (uint256) { return 0; } } diff --git a/hardhat-fork.config.ts b/hardhat-fork.config.ts index 7988525b..15acdaa9 100644 --- a/hardhat-fork.config.ts +++ b/hardhat-fork.config.ts @@ -11,4 +11,7 @@ export default { }, }, }, + mocha: { + timeout: 240000, // 4 min timeout + }, }; diff --git a/tasks/utils/deploy-utils.ts b/tasks/utils/deploy-utils.ts index dbba6c99..e2de5eca 100644 --- a/tasks/utils/deploy-utils.ts +++ b/tasks/utils/deploy-utils.ts @@ -77,7 +77,7 @@ export function logContracts(contracts: { [key: string]: { address: string } }) console.log(`~~~~~~~~~~~~~~~~~~~~~~~~~~~\n`); keys.map(k => { if (Array.isArray(contracts[k])) { - console.log(`${k}:\t[${(contracts[k] as any as [{ address: string }]).map(i => i.address)}]`); + console.log(`${k}:\t[${(contracts[k] as unknown as [{ address: string }]).map(i => i.address)}]`); } else { console.log(`${k}:\t${contracts[k].address}`); } diff --git a/test-fork/FullDeployment.spec.ts b/test-fork/FullDeployment.spec.ts index d69dfd66..d4b9817c 100644 --- a/test-fork/FullDeployment.spec.ts +++ b/test-fork/FullDeployment.spec.ts @@ -57,6 +57,7 @@ import { ethers } from "ethers"; import MerkleTree from "merkletreejs"; const debug = false; +const sleep = (ms: number): Promise => new Promise(resolve => setTimeout(resolve, ms)); const testAccounts = { swapper: "0x0000000000000000000000000000000000000002", @@ -91,6 +92,8 @@ describe("Full Deployment", () => { // TODO - should have sufficient balances on acc, remove this before final test await setupBalances(); deployer = await impersonate(deployerAddress); + // + await sleep(30000); // 30 seconds to avoid max tx issues when doing full deployment }); const getCrv = async (recipient: string, amount = simpleToExactAmount(250)) => { diff --git a/test-fork/RewardPoolDepositWrapper.spec.ts b/test-fork/RewardPoolDepositWrapper.spec.ts index 2f865955..7e602052 100644 --- a/test-fork/RewardPoolDepositWrapper.spec.ts +++ b/test-fork/RewardPoolDepositWrapper.spec.ts @@ -18,6 +18,7 @@ import { expect } from "chai"; import { JoinPoolRequestStruct } from "types/generated/IVault"; const debug = false; +const sleep = (ms: number): Promise => new Promise(resolve => setTimeout(resolve, ms)); const usdcWhale = "0x47ac0Fb4F2D84898e4D9E7b4DaB3C24507a6D503"; const usdcAddress = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"; @@ -36,7 +37,6 @@ describe("RewardPoolDepositWrapper", () => { let usdc: ERC20; before(async () => { - console.log("i"); await network.provider.request({ method: "hardhat_reset", params: [ @@ -48,15 +48,13 @@ describe("RewardPoolDepositWrapper", () => { }, ], }); - console.log("ii"); + deployerAddress = "0xA28ea848801da877E1844F954FF388e857d405e5"; await setupBalances(); + await sleep(30000); // 30 seconds to avoid max tx issues when doing full deployment - console.log("iii"); deployer = await impersonate(deployerAddress); - console.log("iv"); phase1 = await config.getPhase1(deployer); - console.log("v"); phase2 = await deployPhase2( hre, deployer, @@ -67,27 +65,18 @@ describe("RewardPoolDepositWrapper", () => { config.addresses, debug, ); - console.log("vi"); await getWeth(phase2.balLiquidityProvider.address, simpleToExactAmount(500)); - console.log("vii"); phase3 = await deployPhase3(hre, deployer, phase2, config.multisigs, config.addresses, debug); - console.log("viii"); const daoMultisig = await impersonateAccount(config.multisigs.daoMultisig); - console.log("iix"); const tx = await phase3.poolManager.connect(daoMultisig.signer).setProtectPool(false); - console.log("ix"); await waitForTx(tx, debug); - console.log("x"); phase4 = await deployPhase4(hre, deployer, phase3, config.addresses, debug); - console.log("xi"); staker = await impersonate(stakerAddress); - console.log("xii"); usdc = ERC20__factory.connect(usdcAddress, staker); - console.log("xiii"); await getUSDC(stakerAddress, simpleToExactAmount(100, 6)); }); diff --git a/test/AuraPenaltyForwarder.spec.ts b/test/AuraPenaltyForwarder.spec.ts index 1e58b0ff..9b1f1bef 100644 --- a/test/AuraPenaltyForwarder.spec.ts +++ b/test/AuraPenaltyForwarder.spec.ts @@ -3,7 +3,7 @@ import { Signer } from "ethers"; import hre, { ethers } from "hardhat"; import { deployMocks, getMockDistro, getMockMultisigs } from "../scripts/deployMocks"; import { deployPhase1, deployPhase2, deployPhase3, deployPhase4, SystemDeployed } from "../scripts/deploySystem"; -import { ONE_WEEK } from "../test-utils/constants"; +import { ONE_WEEK, ZERO_ADDRESS } from "../test-utils/constants"; import { impersonateAccount } from "../test-utils/fork"; import { BN, simpleToExactAmount } from "../test-utils/math"; import { getTimestamp, increaseTime } from "../test-utils/time"; @@ -62,6 +62,7 @@ describe("AuraPenaltyForwarder", () => { expect(await penaltyForwarder.token(), "token").to.eq(cvx.address); expect(await penaltyForwarder.distributionDelay(), "distributionDelay").to.eq(ONE_WEEK.mul(7).div(2)); expect(await penaltyForwarder.lastDistribution(), "lastDistribution").to.lte(currentTime); + expect(await penaltyForwarder.owner(), "owner").to.eq(await deployer.getAddress()); }); it("forwarder cvx allowance is correct", async () => { expect(await cvx.allowance(penaltyForwarder.address, distributor.address), "allowance").to.eq(0); @@ -111,4 +112,27 @@ describe("AuraPenaltyForwarder", () => { expect(distributorBalanceAfter, "distributor balance").to.eq(distributorBalanceBefore.add(cvxAmount)); }); }); + describe("sets distributor", async () => { + it("fails if the caller is not the owner", async () => { + await expect( + penaltyForwarder.connect(alice).setDistributor(distributor.address), + "fails due to ", + ).to.be.revertedWith("Ownable: caller is not the owner"); + }); + it("sets the new distributor", async () => { + const distributorOld = await penaltyForwarder.distributor(); + + let tx = await penaltyForwarder.setDistributor(ZERO_ADDRESS); + await expect(tx).to.emit(penaltyForwarder, "DistributorChanged").withArgs(ZERO_ADDRESS); + + expect(await penaltyForwarder.distributor(), "distributor").to.eq(ZERO_ADDRESS); + + // Returns original value + + tx = await penaltyForwarder.setDistributor(distributorOld); + await expect(tx).to.emit(penaltyForwarder, "DistributorChanged").withArgs(distributorOld); + + expect(await penaltyForwarder.distributor(), "distributor").to.eq(distributor.address); + }); + }); }); diff --git a/test/ExtraRewardsDistributor.spec.ts b/test/ExtraRewardsDistributor.spec.ts index 171bba56..c7a3e4d9 100644 --- a/test/ExtraRewardsDistributor.spec.ts +++ b/test/ExtraRewardsDistributor.spec.ts @@ -25,6 +25,7 @@ describe("ExtraRewardsDistributor", () => { let aliceAddress: string; let bob: Signer; let bobAddress: string; + let rob: Signer; before(async () => { accounts = await ethers.getSigners(); @@ -51,6 +52,7 @@ describe("ExtraRewardsDistributor", () => { aliceAddress = await alice.getAddress(); bob = accounts[2]; bobAddress = await bob.getAddress(); + rob = accounts[3]; distributor = contracts.extraRewardsDistributor.connect(alice); auraLocker = contracts.cvxLocker.connect(alice); @@ -90,6 +92,7 @@ describe("ExtraRewardsDistributor", () => { it("initial configuration is correct", async () => { expect(await distributor.auraLocker(), "auraLocker").to.eq(auraLocker.address); + expect(await distributor.owner(), "owner").to.eq(await deployer.getAddress()); }); it("add rewards", async () => { @@ -107,6 +110,14 @@ describe("ExtraRewardsDistributor", () => { ); expect(await mockErc20.balanceOf(aliceAddress), "alice balance").to.eq(senderBalanceBefore.sub(fundAmount)); }); + describe("fails", async () => { + it("if adds 0 rewards", async () => { + await expect(distributor.addReward(mockErc20.address, 0)).to.be.revertedWith("!amount"); + }); + it("if non auth adds rewards", async () => { + await expect(distributor.connect(rob).addReward(mockErc20.address, 10)).to.be.revertedWith("!auth"); + }); + }); describe("adds rewards", async () => { it("allows anyone to fund", async () => { await increaseTime(ONE_WEEK); From e7eefe924f84f7a9873668b6700f2bbbc1c2559b Mon Sep 17 00:00:00 2001 From: 0xahtle7 <100377534+0xahtle7@users.noreply.github.com> Date: Thu, 2 Jun 2022 17:55:34 +0100 Subject: [PATCH 19/20] test: increase test coverage (#86) * test: increase test coverage on AuraMerkleDrop * test: increase test coverage on BalLiquidityProvider * test: increase test coverage on AuraBalRewardPool --- contracts/AuraBalRewardPool.sol | 2 +- contracts/AuraLocker.sol | 1 + test/Aura.spec.ts | 22 +++++++--- test/AuraBalRewardPool.spec.ts | 67 +++++++++++++++++++++++++++++++ test/AuraLocker.spec.ts | 43 +++++++++++++++++++- test/AuraMerkleDrop.spec.ts | 34 ++++++++++++++++ test/AuraStakingProxy.spec.ts | 4 ++ test/AuraVestedEscrow.spec.ts | 9 ++++- test/BalLiquidityProvider.spec.ts | 8 ++++ 9 files changed, 181 insertions(+), 9 deletions(-) diff --git a/contracts/AuraBalRewardPool.sol b/contracts/AuraBalRewardPool.sol index 6145a819..09738595 100644 --- a/contracts/AuraBalRewardPool.sol +++ b/contracts/AuraBalRewardPool.sol @@ -71,7 +71,7 @@ contract AuraBalRewardPool { rewardManager = _rewardManager; require(_auraLocker != address(0), "!locker"); auraLocker = IAuraLocker(_auraLocker); - require(_penaltyForwarder != address(0), "!locker"); + require(_penaltyForwarder != address(0), "!forwarder"); penaltyForwarder = _penaltyForwarder; require(_startDelay > 4 days && _startDelay < 2 weeks, "!delay"); diff --git a/contracts/AuraLocker.sol b/contracts/AuraLocker.sol index 685f0a0a..a112e907 100644 --- a/contracts/AuraLocker.sol +++ b/contracts/AuraLocker.sol @@ -192,6 +192,7 @@ contract AuraLocker is ReentrancyGuard, Ownable, IAuraLocker { modifier notBlacklisted(address _sender, address _receiver) { uint256 csS; uint256 csR; + // solhint-disable-next-line no-inline-assembly assembly { csS := extcodesize(_sender) csR := extcodesize(_receiver) diff --git a/test/Aura.spec.ts b/test/Aura.spec.ts index 0465b4a4..ab7fc28e 100644 --- a/test/Aura.spec.ts +++ b/test/Aura.spec.ts @@ -3,7 +3,7 @@ import { BigNumberish, Signer } from "ethers"; import { expect } from "chai"; import { deployPhase1, deployPhase2, deployPhase3, deployPhase4 } from "../scripts/deploySystem"; import { deployMocks, DeployMocksResult, getMockDistro, getMockMultisigs } from "../scripts/deployMocks"; -import { Booster, VoterProxy, AuraToken, AuraMinter } from "../types/generated"; +import { Booster, VoterProxy, AuraToken, AuraMinter, AuraToken__factory } from "../types/generated"; import { DEAD_ADDRESS, simpleToExactAmount, ZERO_ADDRESS } from "../test-utils"; import { impersonateAccount } from "../test-utils/fork"; import { Account } from "types"; @@ -65,6 +65,7 @@ describe("AuraToken", () => { // Expects to be pre-mined with 50 m tokens. (as per deployment script) expect(await cvx.totalSupply()).to.eq(simpleToExactAmount(EMISSIONS_INIT_SUPPLY)); expect(await cvx.EMISSIONS_MAX_SUPPLY()).to.equal(simpleToExactAmount(EMISSIONS_MAX_SUPPLY)); + expect(await cvx.INIT_MINT_AMOUNT()).to.equal(simpleToExactAmount(EMISSIONS_INIT_SUPPLY)); expect(await cvx.reductionPerCliff()).to.equal(simpleToExactAmount(EMISSIONS_MAX_SUPPLY).div(500)); }); describe("@method AuraToken.init fails if ", async () => { @@ -72,13 +73,16 @@ describe("AuraToken", () => { await expect(cvx.connect(deployer).init(DEAD_ADDRESS, DEAD_ADDRESS)).to.revertedWith("Only operator"); }); it("called more than once", async () => { - await expect(cvx.init(DEAD_ADDRESS, DEAD_ADDRESS)).to.revertedWith("Only operator"); - }); - it("wrong amount of tokens", async () => { - await expect(cvx.init(DEAD_ADDRESS, DEAD_ADDRESS)).to.revertedWith("Only operator"); + const operator = await impersonateAccount(await cvx.operator()); + expect(await cvx.totalSupply()).to.not.eq(0); + await expect(cvx.connect(operator.signer).init(DEAD_ADDRESS, DEAD_ADDRESS)).to.revertedWith("Only once"); }); it("wrong minter address", async () => { - await expect(cvx.init(DEAD_ADDRESS, DEAD_ADDRESS)).to.revertedWith("Only operator"); + const auraToken = await new AuraToken__factory(deployer).deploy(voterProxy.address, "AuraToken", "AURA"); + const operator = await impersonateAccount(await auraToken.operator()); + await expect(auraToken.connect(operator.signer).init(DEAD_ADDRESS, ZERO_ADDRESS)).to.revertedWith( + "Invalid minter", + ); }); }); @@ -87,6 +91,12 @@ describe("AuraToken", () => { expect(previousOperator).eq(booster.address); await expect(cvx.connect(deployer).updateOperator()).to.be.revertedWith("!operator"); }); + it("@method AuraToken.updateOperator only if it is initialized", async () => { + const auraToken = await new AuraToken__factory(deployer).deploy(voterProxy.address, "AuraToken", "AURA"); + const operator = await impersonateAccount(await auraToken.operator()); + expect(await auraToken.totalSupply()).to.eq(0); + await expect(auraToken.connect(operator.signer).updateOperator()).to.be.revertedWith("!init"); + }); it("@method AuraToken.mint does not mint if sender is not the operator", async () => { const beforeBalance = await cvx.balanceOf(aliceAddress); const beforeTotalSupply = await cvx.totalSupply(); diff --git a/test/AuraBalRewardPool.spec.ts b/test/AuraBalRewardPool.spec.ts index ccc44b5c..ea39f0a2 100644 --- a/test/AuraBalRewardPool.spec.ts +++ b/test/AuraBalRewardPool.spec.ts @@ -285,6 +285,17 @@ describe("AuraBalRewardPool", () => { ); }); it("constructor pass wrong arguments", async () => { + await expect( + new AuraBalRewardPool__factory(deployer).deploy( + cvxCrv.address, + contracts.cvx.address, + await deployer.getAddress(), + contracts.cvxLocker.address, + contracts.penaltyForwarder.address, + 0, + ), + "Wrong startDelay < 4 days", + ).revertedWith("!delay"); await expect( new AuraBalRewardPool__factory(deployer).deploy( cvxCrv.address, @@ -294,7 +305,63 @@ describe("AuraBalRewardPool", () => { contracts.penaltyForwarder.address, ONE_WEEK.mul(2), ), + "Wrong startDelay >= 2 weeks", ).revertedWith("!delay"); + await expect( + new AuraBalRewardPool__factory(deployer).deploy( + ZERO_ADDRESS, + contracts.cvx.address, + await deployer.getAddress(), + contracts.cvxLocker.address, + contracts.penaltyForwarder.address, + ONE_WEEK, + ), + "Wrong _stakingToken", + ).revertedWith("!tokens"); + await expect( + new AuraBalRewardPool__factory(deployer).deploy( + contracts.cvx.address, + contracts.cvx.address, + await deployer.getAddress(), + contracts.cvxLocker.address, + contracts.penaltyForwarder.address, + ONE_WEEK, + ), + "Wrong _stakingToken", + ).revertedWith("!tokens"); + await expect( + new AuraBalRewardPool__factory(deployer).deploy( + cvxCrv.address, + contracts.cvx.address, + ZERO_ADDRESS, + contracts.cvxLocker.address, + contracts.penaltyForwarder.address, + ONE_WEEK, + ), + "Wrong _rewardManager", + ).revertedWith("!manager"); + await expect( + new AuraBalRewardPool__factory(deployer).deploy( + cvxCrv.address, + contracts.cvx.address, + await deployer.getAddress(), + ZERO_ADDRESS, + contracts.penaltyForwarder.address, + ONE_WEEK, + ), + "Wrong _auraLocker", + ).revertedWith("!locker"); + await expect( + new AuraBalRewardPool__factory(deployer).deploy( + cvxCrv.address, + contracts.cvx.address, + await deployer.getAddress(), + contracts.cvxLocker.address, + ZERO_ADDRESS, + ONE_WEEK, + ), + "Wrong _penaltyForwarder", + ).revertedWith("!forwarder"); }); }); }); diff --git a/test/AuraLocker.spec.ts b/test/AuraLocker.spec.ts index ca145ec2..6b92a2f9 100644 --- a/test/AuraLocker.spec.ts +++ b/test/AuraLocker.spec.ts @@ -520,6 +520,20 @@ describe("AuraLocker", () => { it("still allows blacklisted EOA's to lock", async () => { await auraLocker.connect(alice).lock(aliceAddress, simpleToExactAmount(10)); }); + it("blocks contracts from depositing for a blacklist smart contract", async () => { + const mockToken = await deployContract( + hre, + new MockERC20__factory(deployer), + "mockToken", + ["mockToken", "mockToken", 18, await deployer.getAddress(), simpleToExactAmount(1000000)], + {}, + false, + ); + await auraLocker.connect(accounts[7]).modifyBlacklist(mockToken.address, true); + await expect(lockor.connect(alice).lockFor(mockToken.address, simpleToExactAmount(10))).to.be.revertedWith( + "blacklisted", + ); + }); it("blocks contracts from depositing when they are blacklisted", async () => { await auraLocker.connect(accounts[7]).modifyBlacklist(lockor.address, true); await expect(lockor.connect(alice).lockFor(bobAddress, simpleToExactAmount(10))).to.be.revertedWith( @@ -1354,7 +1368,7 @@ describe("AuraLocker", () => { before(async () => { await setup(); }); - it("queueNewRewards sender is not a distributor", async () => { + it("@queueNewRewards sender is not a distributor", async () => { await expect(auraLocker.queueNewRewards(cvx.address, 0)).revertedWith("!authorized"); }); it("@queueNewRewards sends wrong amount", async () => { @@ -1411,6 +1425,11 @@ describe("AuraLocker", () => { "Ownable: caller is not the owner", ); }); + it("non admin - modify Blacklist", async () => { + await expect(auraLocker.connect(alice).modifyBlacklist(ZERO_ADDRESS, true)).revertedWith( + "Ownable: caller is not the owner", + ); + }); it("set Kick Incentive with wrong rate", async () => { await expect(auraLocker.connect(accounts[7]).setKickIncentive(501, ZERO)).revertedWith("over max rate"); }); @@ -1432,6 +1451,28 @@ describe("AuraLocker", () => { it("emergency withdraw is call and it is not shutdown", async () => { await expect(auraLocker.emergencyWithdraw()).revertedWith("Must be shutdown"); }); + it("@addReward staking token", async () => { + await expect( + auraLocker.connect(accounts[7]).addReward(await auraLocker.stakingToken(), ZERO_ADDRESS), + ).revertedWith("Cannot add StakingToken as reward"); + }); + it("@addReward reward already exist", async () => { + await auraLocker.connect(accounts[7]).addReward("0x0000000000000000000000000000000000000001", ZERO_ADDRESS); + await expect( + auraLocker.connect(accounts[7]).addReward("0x0000000000000000000000000000000000000001", ZERO_ADDRESS), + ).revertedWith("Reward already exists"); + }); + it("@addReward 5 or more rewards", async () => { + await auraLocker.connect(accounts[7]).addReward("0x0000000000000000000000000000000000000002", ZERO_ADDRESS); + await expect( + auraLocker.connect(accounts[7]).addReward("0x0000000000000000000000000000000000000003", ZERO_ADDRESS), + ).revertedWith("Max rewards length"); + }); + it("@getReward wrong skip index argument", async () => { + await expect( + auraLocker.connect(accounts[7])["getReward(address,bool[])"](aliceAddress, [false, false]), + ).revertedWith("!arr"); + }); }); context("admin", () => { before(async () => { diff --git a/test/AuraMerkleDrop.spec.ts b/test/AuraMerkleDrop.spec.ts index 49b23020..0cb9c205 100644 --- a/test/AuraMerkleDrop.spec.ts +++ b/test/AuraMerkleDrop.spec.ts @@ -116,6 +116,32 @@ describe("AuraMerkleDrop", () => { ), ).to.be.revertedWith("!expiry"); }); + it("if zero address on any argument", async () => { + await expect( + new AuraMerkleDrop__factory(deployer).deploy( + ZERO_ADDRESS, + tree.getHexRoot(), + aura.address, + auraLocker.address, + contracts.penaltyForwarder.address, + ONE_WEEK, + ONE_WEEK.mul(3), + ), + "Wrong _dao", + ).to.be.revertedWith("!dao"); + await expect( + new AuraMerkleDrop__factory(deployer).deploy( + adminAddress, + tree.getHexRoot(), + ZERO_ADDRESS, + auraLocker.address, + contracts.penaltyForwarder.address, + ONE_WEEK, + ONE_WEEK.mul(3), + ), + "Wrong aura", + ).to.be.revertedWith("!aura"); + }); }); describe("basic MerkleDrop interactions", () => { let tree: MerkleTree; @@ -338,6 +364,10 @@ describe("AuraMerkleDrop", () => { await expect(tx).to.emit(merkleDrop, "RootSet").withArgs(newRoot); expect(await merkleDrop.merkleRoot()).to.eq(newRoot); }); + it("rescue rewards", async () => { + const tx = await merkleDrop.connect(admin).rescueReward(); + await expect(tx).to.emit(merkleDrop, "Rescued"); + }); it("starts early the drop ", async () => { const timestamp = await getTimestamp(); const tx = await merkleDrop.connect(admin).startEarly(); @@ -364,12 +394,16 @@ describe("AuraMerkleDrop", () => { await expect(tx).to.emit(merkleDrop, "LockerSet").withArgs(bobAddress); expect(await merkleDrop.auraLocker()).to.eq(bobAddress); }); + it("fails to rescue rewards one week after deployment", async () => { + await expect(merkleDrop.connect(admin).rescueReward()).to.be.revertedWith("too late"); + }); it("fails if admin is not the sender", async () => { await expect(merkleDrop.connect(bob).setDao(bobAddress)).to.be.revertedWith("!auth"); await expect(merkleDrop.connect(bob).setRoot(ethers.constants.HashZero)).to.be.revertedWith("!auth"); await expect(merkleDrop.connect(bob).startEarly()).to.be.revertedWith("!auth"); await expect(merkleDrop.connect(bob).withdrawExpired()).to.be.revertedWith("!auth"); await expect(merkleDrop.connect(bob).setLocker(bobAddress)).to.be.revertedWith("!auth"); + await expect(merkleDrop.connect(bob).rescueReward()).to.be.revertedWith("!auth"); }); it("fails to set a new root if it was previously set ", async () => { await expect(merkleDrop.connect(admin).setRoot(tree.getHexRoot())).to.be.revertedWith("already set"); diff --git a/test/AuraStakingProxy.spec.ts b/test/AuraStakingProxy.spec.ts index d069b2bd..23e8aff8 100644 --- a/test/AuraStakingProxy.spec.ts +++ b/test/AuraStakingProxy.spec.ts @@ -126,6 +126,10 @@ describe("AuraStakingProxy", () => { const tx = contracts.cvxStakingProxy.connect(accounts[2]).rescueToken(ZERO_ADDRESS, ZERO_ADDRESS); await expect(tx).to.revertedWith("!auth"); }); + it("fails to distribute", async () => { + const tx = contracts.cvxStakingProxy.connect(accounts[2])["distribute(uint256)"](0); + await expect(tx).to.be.revertedWith("!auth"); + }); }); describe("when called by owner", () => { it("fails to set crvDepositorWrapper if output bps out of range", async () => { diff --git a/test/AuraVestedEscrow.spec.ts b/test/AuraVestedEscrow.spec.ts index 9b60938d..673f2111 100644 --- a/test/AuraVestedEscrow.spec.ts +++ b/test/AuraVestedEscrow.spec.ts @@ -9,6 +9,7 @@ import { getTimestamp, increaseTime } from "../test-utils/time"; import { BN, simpleToExactAmount } from "../test-utils/math"; import { impersonateAccount } from "../test-utils/fork"; +const debug = false; describe("AuraVestedEscrow", () => { let accounts: Signer[]; @@ -48,7 +49,7 @@ describe("AuraVestedEscrow", () => { multisigs, mocks.namingConfig, mocks.addresses, - true, + debug, ); deployerAddress = await deployer.getAddress(); @@ -90,6 +91,12 @@ describe("AuraVestedEscrow", () => { expect(await vestedEscrow.totalTime()).eq(ONE_WEEK.mul(52)); expect(await vestedEscrow.initialised()).eq(false); }); + it("fails to fund due to wrong array of recipients", async () => { + await expect(vestedEscrow.fund([aliceAddress, bobAddress], [])).to.be.revertedWith("!arr"); + }); + it("fails to fund if it is not the funder", async () => { + await expect(vestedEscrow.connect(alice).fund([], [])).to.be.revertedWith("!funder"); + }); // Funds Alice = 200 and Bob = 100 it("funds an array of recipients", async () => { const balBefore = await aura.balanceOf(vestedEscrow.address); diff --git a/test/BalLiquidityProvider.spec.ts b/test/BalLiquidityProvider.spec.ts index be2725a4..d48f4117 100644 --- a/test/BalLiquidityProvider.spec.ts +++ b/test/BalLiquidityProvider.spec.ts @@ -126,6 +126,14 @@ describe("BalLiquidityProvider", () => { "invalid request", ).to.be.revertedWith("!valid"); }); + it("fails if the pair of tokens is the same", async () => { + joinPoolRequest.assets = [await balLiquidityProvider.startToken(), await balLiquidityProvider.startToken()]; + joinPoolRequest.maxAmountsIn = [simpleToExactAmount(80, 16), simpleToExactAmount(80, 16)]; + await expect( + balLiquidityProvider.connect(deployer).provideLiquidity(poolId, joinPoolRequest), + "invalid request", + ).to.be.revertedWith("!assets"); + }); it("fails if the current balance is greater than the min pair amount", async () => { // Given pair token balance is greater than min pair amount const pairTokenBalance = await pairTokenContract.balanceOf(balLiquidityProvider.address); From 0fb78101ba5c17ead7395395f479603909f4698f Mon Sep 17 00:00:00 2001 From: 0xMaharishi Date: Thu, 2 Jun 2022 20:28:58 +0100 Subject: [PATCH 20/20] chore: move eoa to blacklist and update submodule --- contracts/AuraLocker.sol | 24 ++++++++++++------------ convex-platform | 2 +- test/AuraLocker.spec.ts | 33 +++++++++++++++++++++------------ 3 files changed, 34 insertions(+), 25 deletions(-) diff --git a/contracts/AuraLocker.sol b/contracts/AuraLocker.sol index a112e907..9a1c9793 100644 --- a/contracts/AuraLocker.sol +++ b/contracts/AuraLocker.sol @@ -190,19 +190,12 @@ contract AuraLocker is ReentrancyGuard, Ownable, IAuraLocker { } modifier notBlacklisted(address _sender, address _receiver) { - uint256 csS; - uint256 csR; - // solhint-disable-next-line no-inline-assembly - assembly { - csS := extcodesize(_sender) - csR := extcodesize(_receiver) - } - if (csS != 0) { - require(!blacklist[_sender], "blacklisted"); - } - if (csR != 0) { - require(_sender == _receiver || !blacklist[_receiver], "blacklisted"); + require(!blacklist[_sender], "blacklisted"); + + if (_sender != _receiver) { + require(!blacklist[_receiver], "blacklisted"); } + _; } @@ -211,6 +204,13 @@ contract AuraLocker is ReentrancyGuard, Ownable, IAuraLocker { ****************************************/ function modifyBlacklist(address _account, bool _blacklisted) external onlyOwner { + uint256 cs; + // solhint-disable-next-line no-inline-assembly + assembly { + cs := extcodesize(_account) + } + require(cs != 0, "Must be contract"); + blacklist[_account] = _blacklisted; emit BlacklistModified(_account, _blacklisted); } diff --git a/convex-platform b/convex-platform index 953a658c..c570b332 160000 --- a/convex-platform +++ b/convex-platform @@ -1 +1 @@ -Subproject commit 953a658c2f5edf559de26deb85d7a7871c0fea14 +Subproject commit c570b332a074182d699015c598594e42d0f6b95b diff --git a/test/AuraLocker.spec.ts b/test/AuraLocker.spec.ts index 6b92a2f9..5b4ff67b 100644 --- a/test/AuraLocker.spec.ts +++ b/test/AuraLocker.spec.ts @@ -512,15 +512,29 @@ describe("AuraLocker", () => { await lockor.connect(alice).lock(simpleToExactAmount(10)); await lockor.connect(alice).lockFor(aliceAddress, simpleToExactAmount(10)); }); - it("allows blacklisting", async () => { - const tx = await auraLocker.connect(accounts[7]).modifyBlacklist(aliceAddress, true); - await expect(tx).to.emit(auraLocker, "BlacklistModified").withArgs(aliceAddress, true); - expect(await auraLocker.blacklist(aliceAddress)).eq(true); + it("allows blacklisting of contracts", async () => { + const tx = await auraLocker.connect(accounts[7]).modifyBlacklist(lockor.address, true); + await expect(tx).to.emit(auraLocker, "BlacklistModified").withArgs(lockor.address, true); + expect(await auraLocker.blacklist(lockor.address)).eq(true); }); - it("still allows blacklisted EOA's to lock", async () => { + it("blocks contracts from depositing when they are blacklisted", async () => { + await expect(lockor.connect(alice).lockFor(bobAddress, simpleToExactAmount(10))).to.be.revertedWith( + "blacklisted", + ); + }); + it("blocks users from depositing for blacklisted contracts", async () => { + await expect(auraLocker.connect(alice).lock(lockor.address, simpleToExactAmount(10))).to.be.revertedWith( + "blacklisted", + ); + }); + it("doesn't allow blacklisting of EOA's", async () => { + await expect(auraLocker.connect(accounts[7]).modifyBlacklist(aliceAddress, true)).to.be.revertedWith( + "Must be contract", + ); + expect(await auraLocker.blacklist(aliceAddress)).eq(false); await auraLocker.connect(alice).lock(aliceAddress, simpleToExactAmount(10)); }); - it("blocks contracts from depositing for a blacklist smart contract", async () => { + it("blocks contracts from depositing for a blacklisted smart contract", async () => { const mockToken = await deployContract( hre, new MockERC20__factory(deployer), @@ -529,17 +543,12 @@ describe("AuraLocker", () => { {}, false, ); + await auraLocker.connect(accounts[7]).modifyBlacklist(lockor.address, false); await auraLocker.connect(accounts[7]).modifyBlacklist(mockToken.address, true); await expect(lockor.connect(alice).lockFor(mockToken.address, simpleToExactAmount(10))).to.be.revertedWith( "blacklisted", ); }); - it("blocks contracts from depositing when they are blacklisted", async () => { - await auraLocker.connect(accounts[7]).modifyBlacklist(lockor.address, true); - await expect(lockor.connect(alice).lockFor(bobAddress, simpleToExactAmount(10))).to.be.revertedWith( - "blacklisted", - ); - }); }); context("testing edge scenarios", () => {