Skip to content

Commit

Permalink
Merge pull request #129 from roycoprotocol/vault-fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
corddry authored Nov 21, 2024
2 parents 352e7f5 + 6bd2f03 commit 5414c04
Show file tree
Hide file tree
Showing 9 changed files with 347 additions and 68 deletions.
16 changes: 8 additions & 8 deletions script/Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ pragma solidity ^0.8.13;

import "forge-std/Script.sol";

import {WrappedVault} from "../src/WrappedVault.sol";
import {WrappedVaultFactory} from "../src/WrappedVaultFactory.sol";
import {Points} from "../src/Points.sol";
import {PointsFactory} from"../src/PointsFactory.sol";
import {VaultMarketHub} from "../src/VaultMarketHub.sol";
import {RecipeMarketHub} from "../src/RecipeMarketHub.sol";
import {WeirollWallet} from "../src/WeirollWallet.sol";
import { WrappedVault } from "../src/WrappedVault.sol";
import { WrappedVaultFactory } from "../src/WrappedVaultFactory.sol";
import { Points } from "../src/Points.sol";
import { PointsFactory } from "../src/PointsFactory.sol";
import { VaultMarketHub } from "../src/VaultMarketHub.sol";
import { RecipeMarketHub } from "../src/RecipeMarketHub.sol";
import { WeirollWallet } from "../src/WeirollWallet.sol";

import { MockERC20 } from "test/mocks/MockERC20.sol";
import { MockERC4626 } from "test/mocks/MockERC4626.sol";
Expand All @@ -24,7 +24,7 @@ contract Deploy is Script {
vm.startBroadcast(deployerPrivateKey);

PointsFactory pointsFactory = new PointsFactory(deployerAddress);
// WrappedVaultFactory erc4626iFactory = new WrappedVaultFactory(deployerAddress, 0.01e18, 0.001e18, address(pointsFactory) );
// WrappedVaultFactory wrappedVaultFactory = new WrappedVaultFactory(deployerAddress, 0.01e18, 0.001e18, address(pointsFactory) );

WeirollWallet wwi = new WeirollWallet();
// VaultMarketHub vaultMarketHub = new VaultMarketHub(deployerAddress);
Expand Down
70 changes: 42 additions & 28 deletions src/WrappedVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,24 @@
pragma solidity ^0.8.0;

import { ERC20 } from "lib/solmate/src/tokens/ERC20.sol";
import { InitializableERC20 } from "src/periphery/InitializableERC20.sol";
import { SafeCast } from "src/libraries/SafeCast.sol";
import { SafeTransferLib } from "lib/solmate/src/utils/SafeTransferLib.sol";
import { Owned } from "lib/solmate/src/auth/Owned.sol";
import { Ownable } from "lib/solady/src/auth/Ownable.sol";
import { Points } from "src/Points.sol";
import { PointsFactory } from "src/PointsFactory.sol";
import { FixedPointMathLib } from "lib/solmate/src/utils/FixedPointMathLib.sol";
import { FixedPointMathLib as SoladyMath } from "lib/solady/src/utils/FixedPointMathLib.sol";
import { IWrappedVault } from "src/interfaces/IWrappedVault.sol";
import { WrappedVaultFactory } from "src/WrappedVaultFactory.sol";
import { Initializable } from "lib/openzeppelin-contracts/contracts/proxy/utils/Initializable.sol";

Check warning on line 15 in src/WrappedVault.sol

View workflow job for this annotation

GitHub Actions / Tests (20.x)

imported name Initializable is not used

/// @title WrappedVault
/// @author Jack Corddry, CopyPaste, Shivaansh Kapoor
/// @dev A token inheriting from ERC20Rewards will reward token holders with a rewards token.
/// The rewarded amount will be a fixed wei per second, distributed proportionally to token holders
/// by the size of their holdings.
contract WrappedVault is Owned, ERC20, IWrappedVault {
contract WrappedVault is Ownable, InitializableERC20, IWrappedVault {
using SafeTransferLib for ERC20;
using SafeCast for uint256;
using FixedPointMathLib for uint256;
Expand All @@ -36,7 +39,6 @@ contract WrappedVault is Owned, ERC20, IWrappedVault {
error MaxRewardsReached();
error TooFewShares();
error VaultNotAuthorizedToRewardPoints();
error InvalidInterval();
error IntervalInProgress();
error IntervalScheduled();
error NoIntervalInProgress();
Expand All @@ -48,6 +50,10 @@ contract WrappedVault is Owned, ERC20, IWrappedVault {
error InvalidWithdrawal();
error InvalidIntervalDuration();
error NotOwnerOfVaultOrApproved();
error IntervalEndBeforeStart();
error IntervalEndInPast();
error CannotShortenInterval();
error IntervalStartIsZero();

/*//////////////////////////////////////////////////////////////
STORAGE
Expand Down Expand Up @@ -77,20 +83,20 @@ contract WrappedVault is Owned, ERC20, IWrappedVault {
}

/// @dev The max amount of reward campaigns a user can be involved in
uint256 private constant MAX_REWARDS = 20;
uint256 public constant MAX_REWARDS = 20;
/// @dev The minimum duration a reward campaign must last
uint256 private constant MIN_CAMPAIGN_DURATION = 1 weeks;
uint256 public constant MIN_CAMPAIGN_DURATION = 1 weeks;
/// @dev RewardsPerToken.accumulated is scaled up to prevent loss of incentives
uint256 private constant RPT_PRECISION = 1e27;
uint256 public constant RPT_PRECISION = 1e27;

/// @dev The address of the underlying vault being incentivized
IWrappedVault public immutable VAULT;
IWrappedVault public VAULT;
/// @dev The underlying asset being deposited into the vault
ERC20 private immutable DEPOSIT_ASSET;
ERC20 private DEPOSIT_ASSET;
/// @dev The address of the canonical points program factory
PointsFactory public immutable POINTS_FACTORY;
PointsFactory public POINTS_FACTORY;
/// @dev The address of the canonical WrappedVault factory
WrappedVaultFactory public immutable ERC4626I_FACTORY;
WrappedVaultFactory public WRAPPED_VAULT_FACTORY;

/// @dev The fee taken by the referring frontend, out of WAD
uint256 public frontendFee;
Expand All @@ -109,7 +115,7 @@ contract WrappedVault is Owned, ERC20, IWrappedVault {
mapping(address => mapping(address => uint256)) public rewardToClaimantToFees;

/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
INITIALIZER
//////////////////////////////////////////////////////////////*/

/// @param _owner The owner of the incentivized vault
Expand All @@ -118,19 +124,23 @@ contract WrappedVault is Owned, ERC20, IWrappedVault {
/// @param vault The underlying vault being incentivized
/// @param initialFrontendFee The initial fee set for the frontend out of WAD
/// @param pointsFactory The canonical factory responsible for deploying all points programs
constructor(
function initialize(
address _owner,
string memory _name,
string memory _symbol,
address vault,
uint256 initialFrontendFee,
address pointsFactory
)
Owned(_owner)
ERC20(_name, _symbol, ERC20(vault).decimals())
external
initializer
{
ERC4626I_FACTORY = WrappedVaultFactory(msg.sender);
if (initialFrontendFee < ERC4626I_FACTORY.minimumFrontendFee()) revert FrontendFeeBelowMinimum();
// Initialize child contracts
_initializeOwner(_owner);
_initializeERC20(_name, _symbol, ERC20(vault).decimals());

WRAPPED_VAULT_FACTORY = WrappedVaultFactory(msg.sender);
if (initialFrontendFee < WRAPPED_VAULT_FACTORY.minimumFrontendFee()) revert FrontendFeeBelowMinimum();

frontendFee = initialFrontendFee;
VAULT = IWrappedVault(vault);
Expand All @@ -142,6 +152,8 @@ contract WrappedVault is Owned, ERC20, IWrappedVault {
DEPOSIT_ASSET.approve(vault, type(uint256).max);
}

constructor() { }

Check warning on line 155 in src/WrappedVault.sol

View workflow job for this annotation

GitHub Actions / Tests (20.x)

Code contains empty blocks

/// @param rewardsToken The new reward token / points program to be used as incentives
function addRewardsToken(address rewardsToken) public payable onlyOwner {
// Check if max rewards offered limit has been reached
Expand All @@ -165,7 +177,7 @@ contract WrappedVault is Owned, ERC20, IWrappedVault {

/// @param newFrontendFee The new front-end fee out of WAD
function setFrontendFee(uint256 newFrontendFee) public payable onlyOwner {
if (newFrontendFee < ERC4626I_FACTORY.minimumFrontendFee()) revert FrontendFeeBelowMinimum();
if (newFrontendFee < WRAPPED_VAULT_FACTORY.minimumFrontendFee()) revert FrontendFeeBelowMinimum();
frontendFee = newFrontendFee;
emit FrontendFeeUpdated(newFrontendFee);
}
Expand Down Expand Up @@ -223,17 +235,17 @@ contract WrappedVault is Owned, ERC20, IWrappedVault {
function extendRewardsInterval(address reward, uint256 rewardsAdded, uint256 newEnd, address frontendFeeRecipient) external payable onlyOwner {
if (!isReward[reward]) revert InvalidReward();
RewardsInterval storage rewardsInterval = rewardToInterval[reward];
if (newEnd <= rewardsInterval.end) revert InvalidInterval();
if (newEnd <= rewardsInterval.end) revert CannotShortenInterval();
if (block.timestamp >= rewardsInterval.end) revert NoIntervalInProgress();
_updateRewardsPerToken(reward);

// Calculate fees
uint256 frontendFeeTaken = rewardsAdded.mulWadDown(frontendFee);
uint256 protocolFeeTaken = rewardsAdded.mulWadDown(ERC4626I_FACTORY.protocolFee());
uint256 protocolFeeTaken = rewardsAdded.mulWadDown(WRAPPED_VAULT_FACTORY.protocolFee());

// Make fees available for claiming
rewardToClaimantToFees[reward][frontendFeeRecipient] += frontendFeeTaken;
rewardToClaimantToFees[reward][ERC4626I_FACTORY.protocolFeeRecipient()] += protocolFeeTaken;
rewardToClaimantToFees[reward][WRAPPED_VAULT_FACTORY.protocolFeeRecipient()] += protocolFeeTaken;

// Calculate the new rate

Expand Down Expand Up @@ -265,7 +277,9 @@ contract WrappedVault is Owned, ERC20, IWrappedVault {
/// @param frontendFeeRecipient The address to reward the frontendFee
function setRewardsInterval(address reward, uint256 start, uint256 end, uint256 totalRewards, address frontendFeeRecipient) external payable onlyOwner {
if (!isReward[reward]) revert InvalidReward();
if (start >= end || end <= block.timestamp) revert InvalidInterval();
if (start >= end) revert IntervalEndBeforeStart();
if (end <= block.timestamp) revert IntervalEndInPast();
if (start == 0) revert IntervalStartIsZero();
if ((end - start) < MIN_CAMPAIGN_DURATION) revert InvalidIntervalDuration();

RewardsInterval storage rewardsInterval = rewardToInterval[reward];
Expand All @@ -282,11 +296,11 @@ contract WrappedVault is Owned, ERC20, IWrappedVault {

// Calculate fees
uint256 frontendFeeTaken = totalRewards.mulWadDown(frontendFee);
uint256 protocolFeeTaken = totalRewards.mulWadDown(ERC4626I_FACTORY.protocolFee());
uint256 protocolFeeTaken = totalRewards.mulWadDown(WRAPPED_VAULT_FACTORY.protocolFee());

// Make fees available for claiming
rewardToClaimantToFees[reward][frontendFeeRecipient] += frontendFeeTaken;
rewardToClaimantToFees[reward][ERC4626I_FACTORY.protocolFeeRecipient()] += protocolFeeTaken;
rewardToClaimantToFees[reward][WRAPPED_VAULT_FACTORY.protocolFeeRecipient()] += protocolFeeTaken;

// Calculate the rate
uint256 rate = (totalRewards - frontendFeeTaken - protocolFeeTaken) / (end - start);
Expand Down Expand Up @@ -353,7 +367,7 @@ contract WrappedVault is Owned, ERC20, IWrappedVault {
// The rewards per token are scaled up for precision
uint256 elapsedScaled = elapsed * RPT_PRECISION;
// Calculate and update the new value of the accumulator.
rewardsPerTokenOut.accumulated = (rewardsPerTokenIn.accumulated + (elapsedScaled.mulDivDown(rewardsInterval_.rate, totalSupply)));
rewardsPerTokenOut.accumulated = (rewardsPerTokenIn.accumulated + (SoladyMath.fullMulDiv(elapsedScaled, rewardsInterval_.rate, totalSupply)));

return rewardsPerTokenOut;
}
Expand Down Expand Up @@ -602,8 +616,8 @@ contract WrappedVault is Owned, ERC20, IWrappedVault {
}

/// @inheritdoc IWrappedVault
function maxWithdraw(address) external view returns (uint256 maxAssets) {
maxAssets = VAULT.maxWithdraw(address(this));
function maxWithdraw(address owner) external view returns (uint256 maxAssets) {
return SoladyMath.min(VAULT.convertToAssets(balanceOf[owner]), VAULT.maxWithdraw(address(this)));
}

/// @inheritdoc IWrappedVault
Expand All @@ -612,8 +626,8 @@ contract WrappedVault is Owned, ERC20, IWrappedVault {
}

/// @inheritdoc IWrappedVault
function maxRedeem(address) external view returns (uint256 maxShares) {
maxShares = VAULT.maxRedeem(address(this));
function maxRedeem(address owner) external view returns (uint256 maxShares) {
return SoladyMath.min(balanceOf[owner], VAULT.maxRedeem(address(this)));
}

/// @inheritdoc IWrappedVault
Expand Down
39 changes: 29 additions & 10 deletions src/WrappedVaultFactory.sol
Original file line number Diff line number Diff line change
@@ -1,31 +1,40 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.20;

import { Owned } from "lib/solmate/src/auth/Owned.sol";
import { Ownable2Step, Ownable } from "lib/openzeppelin-contracts/contracts/access/Ownable2Step.sol";
import { ERC4626 } from "lib/solmate/src/tokens/ERC4626.sol";
import { LibString } from "lib/solmate/src/utils/LibString.sol";
import { Clones } from "lib/openzeppelin-contracts/contracts/proxy/Clones.sol";
import { WrappedVault } from "src/WrappedVault.sol";

/// @title WrappedVaultFactory
/// @author CopyPaste, Jack Corddry, Shivaansh Kapoor
/// @dev A factory for deploying wrapped vaults, and managing protocol or other fees
contract WrappedVaultFactory is Owned {
contract WrappedVaultFactory is Ownable2Step {
using Clones for address;

// Address of the Wrapped Vault's implementation contract
address wrappedVaultImplementation;

Check warning on line 17 in src/WrappedVaultFactory.sol

View workflow job for this annotation

GitHub Actions / Tests (20.x)

Explicitly mark visibility of state

/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(
address _wrappedVaultImplementation,
address _protocolFeeRecipient,
uint256 _protocolFee,
uint256 _minimumFrontendFee,
address _owner,
address _pointsFactory
)
payable
Owned(_owner)
Ownable(_owner)
{
if (_wrappedVaultImplementation.code.length == 0) revert InvalidWrappedVaultImplementation();
if (_protocolFee > MAX_PROTOCOL_FEE) revert ProtocolFeeTooHigh();
if (_minimumFrontendFee > MAX_MIN_REFERRAL_FEE) revert ReferralFeeTooHigh();

wrappedVaultImplementation = _wrappedVaultImplementation;
protocolFeeRecipient = _protocolFeeRecipient;
protocolFee = _protocolFee;
minimumFrontendFee = _minimumFrontendFee;
Expand All @@ -36,8 +45,8 @@ contract WrappedVaultFactory is Owned {
STORAGE
//////////////////////////////////////////////////////////////*/

uint256 private constant MAX_PROTOCOL_FEE = 0.3e18;
uint256 private constant MAX_MIN_REFERRAL_FEE = 0.3e18;
uint256 public constant MAX_PROTOCOL_FEE = 0.3e18;
uint256 public constant MAX_MIN_REFERRAL_FEE = 0.3e18;

address public immutable pointsFactory;

Expand All @@ -55,9 +64,11 @@ contract WrappedVaultFactory is Owned {
/*//////////////////////////////////////////////////////////////
INTERFACE
//////////////////////////////////////////////////////////////*/
error InvalidWrappedVaultImplementation();
error ProtocolFeeTooHigh();
error ReferralFeeTooHigh();

event WrappedVaultImplementationUpdated(address newWrappedVaultImplementation);
event ProtocolFeeUpdated(uint256 newProtocolFee);
event ReferralFeeUpdated(uint256 newReferralFee);
event ProtocolFeeRecipientUpdated(address newRecipient);
Expand All @@ -75,6 +86,13 @@ contract WrappedVaultFactory is Owned {
OWNER CONTROLS
//////////////////////////////////////////////////////////////*/

/// @param newWrappedVaultImplementation The new address of the Wrapped Vault implementation.
function updateWrappedVaultImplementation(address newWrappedVaultImplementation) external payable onlyOwner {
if (newWrappedVaultImplementation.code.length == 0) revert InvalidWrappedVaultImplementation();
wrappedVaultImplementation = newWrappedVaultImplementation;
emit WrappedVaultImplementationUpdated(newWrappedVaultImplementation);
}

/// @param newProtocolFee The new protocol fee to set for a given vault, must be less than MAX_PROTOCOL_FEE
function updateProtocolFee(uint256 newProtocolFee) external payable onlyOwner {
if (newProtocolFee > MAX_PROTOCOL_FEE) revert ProtocolFeeTooHigh();
Expand All @@ -99,14 +117,15 @@ contract WrappedVaultFactory is Owned {
VAULT CREATION
//////////////////////////////////////////////////////////////*/

/// @param vault The ERC4626 Vault to wrap
/// @param owner The address of the wrapped vault owner
/// @param name The name of the wrapped vault
/// @param initialFrontendFee The initial frontend fee for the wrapped vault ()
/// @param vault The ERC4626 Vault to wrap.
/// @param owner The address of the wrapped vault owner.
/// @param name The name of the wrapped vault.
/// @param initialFrontendFee The initial frontend fee for the wrapped vault.
function wrapVault(ERC4626 vault, address owner, string calldata name, uint256 initialFrontendFee) external payable returns (WrappedVault wrappedVault) {
string memory newSymbol = getNextSymbol();
bytes32 salt = keccak256(abi.encodePacked(address(vault), owner, name, initialFrontendFee));
wrappedVault = new WrappedVault{ salt: salt }(owner, name, newSymbol, address(vault), initialFrontendFee, pointsFactory);
wrappedVault = WrappedVault(wrappedVaultImplementation.cloneDeterministic(salt));
wrappedVault.initialize(owner, name, newSymbol, address(vault), initialFrontendFee, pointsFactory);

incentivizedVaults.push(address(wrappedVault));
isVault[address(wrappedVault)] = true;
Expand Down
Loading

0 comments on commit 5414c04

Please sign in to comment.