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

build: V3 #3

Open
wants to merge 3 commits 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ reports/

# Node/npm
node_modules/
venv/
2 changes: 0 additions & 2 deletions brownie-config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ autofetch_sources: True

# require OpenZepplin Contracts
dependencies:
- yearn/[email protected]
- OpenZeppelin/[email protected]
- OpenZeppelin/[email protected]

Expand All @@ -17,7 +16,6 @@ compiler:
solc:
version:
remappings:
- "@yearnvaults=yearn/[email protected]"
- "@openzeppelin=OpenZeppelin/[email protected]"
- "@openzeppelin_new=OpenZeppelin/[email protected]"

Expand Down
30 changes: 27 additions & 3 deletions contracts/StakingRewards.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ contract StakingRewards is
IERC20 public stakingToken;

/// @notice The end (timestamp) of our current or most recent reward period.
uint256 public periodFinish = 0;
uint256 public periodFinish;

/// @notice The distribution rate of rewardsToken per second.
uint256 public rewardRate = 0;
uint256 public rewardRate;

/// @notice The duration of our rewards distribution for staking, default is 7 days.
uint256 public rewardsDuration = 7 days;
uint256 public rewardsDuration;

/// @notice The last time rewards were updated, triggered by updateReward() or notifyRewardAmount().
/// @dev Will be the timestamp of the update or the end of the period, whichever is earlier.
Expand Down Expand Up @@ -73,10 +73,34 @@ contract StakingRewards is
address _stakingToken,
address _zapContract
) public Owned(_owner) {
initialize(
_owner,
_rewardsDistribution,
_rewardsToken,
_stakingToken,
_zapContract
);
}

function initialize(
address _owner,
address _rewardsDistribution,
address _rewardsToken,
address _stakingToken,
address _zapContract
) public {
// make sure that we haven't initialized this before
require(address(rewardsToken) == address(0), "initialized");

owner = _owner;
// set up our state vars
rewardsToken = IERC20(_rewardsToken);
stakingToken = IERC20(_stakingToken);
rewardsDistribution = _rewardsDistribution;
zapContract = _zapContract;

// default duration to 7 days
rewardsDuration = 7 days;
}

/* ========== VIEWS ========== */
Expand Down
125 changes: 124 additions & 1 deletion contracts/StakingRewardsRegistry.sol
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.15;
pragma solidity 0.8.18;

import "@openzeppelin_new/contracts/access/Ownable.sol";

interface IStakingRewards {
function stakingToken() external view returns (address);

function owner() external view returns (address);

function initialize(
address _owner,
address _rewardsDistribution,
address _rewardsToken,
address _stakingToken,
address _zapContract
) external;
}

contract StakingRewardsRegistry is Ownable {
Expand All @@ -31,11 +39,23 @@ contract StakingRewardsRegistry is Ownable {
/// @notice Check if an address can add pools to this registry.
mapping(address => bool) public poolEndorsers;

/// @notice Zapper contract to user.
address public zapper;

/// @notice Original Staking Rewards contract to clone.
address public immutable original;

/* ========== EVENTS ========== */

event StakingPoolAdded(address indexed token, address indexed stakingPool);
event ApprovedPoolOwnerUpdated(address governance, bool approved);
event ApprovedPoolEndorser(address account, bool canEndorse);
event ZapContractUpdated(address _zapContract);

constructor(address _originalStaker, address _zapContract) {
original = _originalStaker;
zapper = _zapContract;
}

/* ========== VIEWS ========== */

Expand Down Expand Up @@ -65,7 +85,26 @@ contract StakingRewardsRegistry is Ownable {
) external {
// don't let just anyone add to our registry
require(poolEndorsers[msg.sender], "unauthorized");
_addStakingPool(_stakingPool, _token, _replaceExistingPool);
}

/**
@notice
Add a new staking pool to our registry, for new or existing tokens.
@dev
Throws if governance isn't set properly.
Throws if sender isn't allowed to endorse.
Throws if replacement is handled improperly.
Emits a StakingPoolAdded event.
@param _stakingPool The address of the new staking pool.
@param _token The token to be deposited into the new staking pool.
@param _replaceExistingPool If we are replacing an existing staking pool, set this to true.
*/
function _addStakingPool(
address _stakingPool,
address _token,
bool _replaceExistingPool
) internal {
// load up the staking pool contract
IStakingRewards stakingRewards = IStakingRewards(_stakingPool);

Expand Down Expand Up @@ -102,6 +141,81 @@ contract StakingRewardsRegistry is Ownable {
emit StakingPoolAdded(_token, _stakingPool);
}

/* ========== CLONING ========== */

event Cloned(address indexed clone);

/**
@notice Used for owner to clone an exact copy of this staking pool and add to registry.
@dev Note that owner will have to call acceptOwnership() to assume ownership of the new staking pool.
@param _rewardsToken Address of our rewards token.
@param _stakingToken Address of our staking token.
*/
function cloneAndAddStakingPool(
address _rewardsToken,
address _stakingToken
) external onlyOwner returns (address newStakingPool) {
// Clone new pool.
newStakingPool = cloneStakingPool(
owner(),
owner(),
_rewardsToken,
_stakingToken,
zapper
);

// Add to the registry.
_addStakingPool(
newStakingPool,
_stakingToken,
isRegistered[_stakingToken]
);
}

/**
@notice Use this to clone an exact copy of this staking pool.
@dev Note that owner will have to call acceptOwnership() to assume ownership of the new staking pool.
@param _owner Owner of the new staking contract.
@param _rewardsDistribution Only this address can call notifyRewardAmount, to add more rewards.
@param _rewardsToken Address of our rewards token.
@param _stakingToken Address of our staking token.
@param _zapContract Address of our zap contract.
*/
function cloneStakingPool(
address _owner,
address _rewardsDistribution,
address _rewardsToken,
address _stakingToken,
address _zapContract
) public returns (address newStakingPool) {
// Copied from https://github.com/optionality/clone-factory/blob/master/contracts/CloneFactory.sol
bytes20 addressBytes = bytes20(original);
assembly {
// EIP-1167 bytecode
let clone_code := mload(0x40)
mstore(
clone_code,
0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000
)
mstore(add(clone_code, 0x14), addressBytes)
mstore(
add(clone_code, 0x28),
0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000
)
newStakingPool := create(0, clone_code, 0x37)
}

IStakingRewards(newStakingPool).initialize(
_owner,
_rewardsDistribution,
_rewardsToken,
_stakingToken,
_zapContract
);

emit Cloned(newStakingPool);
}

/* ========== SETTERS ========== */

/**
Expand Down Expand Up @@ -131,4 +245,13 @@ contract StakingRewardsRegistry is Ownable {
approvedPoolOwner[_addr] = _approved;
emit ApprovedPoolOwnerUpdated(_addr, _approved);
}

/// @notice Set our zap contract.
/// @dev May only be called by owner, and can't be set to zero address.
/// @param _zapContract Address of the new zap contract.
function setZapContract(address _zapContract) external onlyOwner {
require(_zapContract != address(0), "no zero address");
zapper = _zapContract;
emit ZapContractUpdated(_zapContract);
}
}
50 changes: 26 additions & 24 deletions contracts/StakingRewardsZap.sol
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.15;
pragma solidity 0.8.18;

import "@openzeppelin_new/contracts/access/Ownable.sol";
import "@openzeppelin_new/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin_new/contracts/token/ERC20/utils/SafeERC20.sol";

interface IVault is IERC20 {
function token() external view returns (address);
function asset() external view returns (address);

function deposit(uint256, address) external returns (uint256);
}
Expand Down Expand Up @@ -38,37 +38,39 @@ contract StakingRewardsZap is Ownable {
event UpdatedPoolRegistry(address registry);
event Recovered(address token, uint256 amount);

/* ========== CONSTRUCTOR ========== */

constructor(address _stakingPoolRegistry) {
stakingPoolRegistry = _stakingPoolRegistry;
}

/* ========== MUTATIVE FUNCTIONS ========== */

function zapIn(address _targetVault, uint256 _underlyingAmount)
external
returns (uint256)
{
// get our staking pool from our registry for this vault token
IRegistry poolRegistry = IRegistry(stakingPoolRegistry);

// check what our address is, make sure it's not zero
address _vaultStakingPool = poolRegistry.stakingPool(_targetVault);
require(_vaultStakingPool != address(0), "staking pool doesn't exist");
IStakingRewards vaultStakingPool = IStakingRewards(_vaultStakingPool);
IStakingRewards _vaultStakingPool =
IStakingRewards(
IRegistry(stakingPoolRegistry).stakingPool(_targetVault)
);
require(
address(_vaultStakingPool) != address(0),
"staking pool does not exist"
);

// get our underlying token
IVault targetVault = IVault(_targetVault);
IERC20 underlying = IERC20(targetVault.token());
IERC20 underlying = IERC20(IVault(_targetVault).asset());

// transfer to zap and deposit underlying to vault, but first check our approvals and store starting amount
_checkAllowance(_targetVault, address(underlying), _underlyingAmount);
uint256 beforeAmount = underlying.balanceOf(address(this));
underlying.transferFrom(msg.sender, address(this), _underlyingAmount);

underlying.safeTransferFrom(
msg.sender,
address(this),
_underlyingAmount
);

// Check allowance to the vault.
_checkAllowance(_targetVault, address(underlying), _underlyingAmount);
// deposit only our underlying amount, make sure deposit worked
uint256 toStake = targetVault.deposit(_underlyingAmount, address(this));
uint256 toStake =
IVault(_targetVault).deposit(_underlyingAmount, address(this));

// this shouldn't be reached thanks to vault checks, but leave it in case vault code changes
require(
Expand All @@ -77,11 +79,11 @@ contract StakingRewardsZap is Ownable {
);

// make sure we have approved the staking pool, as they can be added/updated at any time
_checkAllowance(_vaultStakingPool, _targetVault, toStake);
_checkAllowance(address(_vaultStakingPool), _targetVault, toStake);

// stake for our user, return the amount we staked
vaultStakingPool.stakeFor(msg.sender, toStake);
emit ZapIn(msg.sender, address(targetVault), toStake);
_vaultStakingPool.stakeFor(msg.sender, toStake);
emit ZapIn(msg.sender, _targetVault, toStake);
return toStake;
}

Expand All @@ -91,8 +93,8 @@ contract StakingRewardsZap is Ownable {
uint256 _amount
) internal {
if (IERC20(_token).allowance(address(this), _contract) < _amount) {
IERC20(_token).safeApprove(_contract, 0);
IERC20(_token).safeApprove(_contract, type(uint256).max);
IERC20(_token).approve(_contract, 0);
IERC20(_token).approve(_contract, type(uint256).max);
}
}

Expand Down
Loading