Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

All code4rena fixes #6

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 9 additions & 15 deletions contracts/Aura.sol
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
// 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 { 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 {
Expand All @@ -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;

Expand Down Expand Up @@ -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;
Expand All @@ -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;
}
Expand All @@ -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);
Expand Down
37 changes: 31 additions & 6 deletions contracts/AuraBalRewardPool.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.11;
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";

Expand All @@ -21,7 +20,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;
Expand All @@ -30,7 +29,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;
Expand All @@ -50,6 +49,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
Expand All @@ -67,14 +67,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;
}

Expand Down Expand Up @@ -178,6 +181,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;
Expand All @@ -199,6 +203,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
*/
Expand Down
8 changes: 3 additions & 5 deletions contracts/AuraClaimZap.sol
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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);
}
}
}
Expand Down
97 changes: 65 additions & 32 deletions contracts/AuraLocker.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.11;
pragma experimental ABIEncoderV2;
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";
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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");

Expand Down Expand Up @@ -318,21 +343,35 @@ 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();
}

//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) }));
}
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
}

Expand All @@ -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];

Expand All @@ -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();

Expand Down
2 changes: 1 addition & 1 deletion contracts/AuraMath.sol
Original file line number Diff line number Diff line change
@@ -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).
Expand Down
Loading