Skip to content

Commit

Permalink
add sponsorship vault extension
Browse files Browse the repository at this point in the history
  • Loading branch information
trmid committed Jul 22, 2024
1 parent 6c096d7 commit 1611381
Show file tree
Hide file tree
Showing 3 changed files with 218 additions and 1 deletion.
2 changes: 1 addition & 1 deletion src/PrizeVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -749,7 +749,7 @@ contract PrizeVault is TwabERC20, Claimable, IERC4626, ILiquidationSource, Ownab
address _tokenIn,
uint256 _amountIn,
bytes calldata /* transferTokensOutData */
) external onlyLiquidationPair {
) external virtual onlyLiquidationPair {
address _prizeToken = address(prizePool.prizeToken());
if (_tokenIn != _prizeToken) {
revert LiquidationTokenInNotPrizeToken(_tokenIn, _prizeToken);
Expand Down
71 changes: 71 additions & 0 deletions src/extensions/SponsorshipVault.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import { PrizeVault, IERC4626, PrizePool, ILiquidationSource } from "../PrizeVault.sol";

/// @title PoolTogether V5 Sponsorship Vault
/// @author G9 Software Inc.
/// @notice Creates a prize vault that contributes prizes on behalf of a `contributionBeneficiary`
/// address rather than on behalf of itself. This allows someone to create a vault with the asset
/// and yield source of their choice in order to sponsor a different vault with the generated
/// yield.
contract SponsorshipVault is PrizeVault {

/// @notice The address that yield is contributed to the prize pool on behalf of.
address public immutable contributionBeneficiary;

/// @notice Sponsorship vault constructor
/// @param name_ Name of the ERC20 share minted by the vault
/// @param symbol_ Symbol of the ERC20 share minted by the vault
/// @param yieldVault_ Address of the underlying ERC4626 vault in which assets are deposited to generate yield
/// @param prizePool_ Address of the PrizePool that computes prizes
/// @param claimer_ Address of the claimer
/// @param yieldFeeRecipient_ Address of the yield fee recipient
/// @param yieldFeePercentage_ Yield fee percentage
/// @param yieldBuffer_ Amount of yield to keep as a buffer
/// @param owner_ Address that will gain ownership of this contract
/// @param contributionBeneficiary_ Address that will be sponsored with prize pool contributions.
constructor(
string memory name_,
string memory symbol_,
IERC4626 yieldVault_,
PrizePool prizePool_,
address claimer_,
address yieldFeeRecipient_,
uint32 yieldFeePercentage_,
uint256 yieldBuffer_,
address owner_,
address contributionBeneficiary_
) PrizeVault(
name_,
symbol_,
yieldVault_,
prizePool_,
claimer_,
yieldFeeRecipient_,
yieldFeePercentage_,
yieldBuffer_,
owner_
) {
assert(contributionBeneficiary_ != address(0));
assert(contributionBeneficiary_ != address(this));
contributionBeneficiary = contributionBeneficiary_;
}

/// @inheritdoc ILiquidationSource
/// @dev Contributes prize tokens on behalf of the `contributionBeneficiary` if set. Otherwise,
/// contributes on behalf of this vault.
function verifyTokensIn(
address _tokenIn,
uint256 _amountIn,
bytes calldata /* transferTokensOutData */
) external override onlyLiquidationPair {
address _prizeToken = address(prizePool.prizeToken());
if (_tokenIn != _prizeToken) {
revert LiquidationTokenInNotPrizeToken(_tokenIn, _prizeToken);
}

prizePool.contributePrizeTokens(contributionBeneficiary, _amountIn);
}

}
146 changes: 146 additions & 0 deletions src/extensions/SponsorshipVaultFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import { IERC20, IERC4626 } from "openzeppelin/token/ERC20/extensions/ERC4626.sol";
import { SafeERC20 } from "openzeppelin/token/ERC20/utils/SafeERC20.sol";

import { PrizePool } from "pt-v5-prize-pool/PrizePool.sol";

import { SponsorshipVault } from "./SponsorshipVault.sol";

/// @title PoolTogether V5 Sponsorship Vault Factory
/// @author PoolTogether Inc. & G9 Software Inc.
/// @notice Factory contract for deploying new sponsorship vaults using a standard underlying ERC4626 yield vault.
contract SponsorshipVaultFactory {
using SafeERC20 for IERC20;

////////////////////////////////////////////////////////////////////////////////
// Events
////////////////////////////////////////////////////////////////////////////////

/// @notice Emitted when a new SponsorshipVault has been deployed by this factory.
/// @param vault The vault that was deployed
/// @param yieldVault The underlying yield vault
/// @param prizePool The prize pool the vault contributes to
/// @param contributionBeneficiary The address that will be sponsored
/// @param name The name of the vault token
/// @param symbol The symbol for the vault token
event NewSponsorshipVault(
SponsorshipVault indexed vault,
IERC4626 indexed yieldVault,
PrizePool indexed prizePool,
address contributionBeneficiary,
string name,
string symbol
);

////////////////////////////////////////////////////////////////////////////////
// Variables
////////////////////////////////////////////////////////////////////////////////

/// @notice List of all vaults deployed by this factory.
SponsorshipVault[] public allVaults;

/// @notice Mapping to verify if a Vault has been deployed via this factory.
mapping(address vault => bool deployedByFactory) public deployedVaults;

/// @notice Mapping to store deployer nonces for CREATE2
mapping(address deployer => uint256 nonce) public deployerNonces;

////////////////////////////////////////////////////////////////////////////////
// External Functions
////////////////////////////////////////////////////////////////////////////////

/// @notice Deploy a new sponsorship vault
/// @dev Emits a `NewSponsorshipVault` event with the vault details.
/// @dev The caller MUST approve this factory to spend underlying assets equal to `YIELD_BUFFER` so the yield
/// buffer can be filled on deployment. This value is unrecoverable and is expected to be insignificant.
/// @dev The yield buffer is expected to be of insignificant value and is used to cover rounding
/// errors on deposits and withdrawals. Yield is expected to accrue faster than the yield buffer
/// can be reasonably depleted.
///
/// The yield buffer should be set as high as possible while still being considered
/// insignificant for the lowest precision per dollar asset that is expected to be supported.
///
/// Precision per dollar (PPD) can be calculated by: (10 ^ DECIMALS) / ($ value of 1 asset).
/// For example, USDC has a PPD of (10 ^ 6) / ($1) = 10e6 p/$.
///
/// As a rule of thumb, assets with lower PPD than USDC should not be assumed to be compatible since
/// the potential loss of a single unit rounding error is likely too high to be made up by yield at
/// a reasonable rate. Actual results may vary based on expected gas costs, asset fluctuation, and
/// yield accrual rates.
///
/// This factory will transfer an amount of assets equal to the yield buffer from the deployer to the
/// sponsorship vault on deployment to cover the initial buffer. For example, if you are deploying a USDC
/// vault and the yield buffer is set to 1e5, you will have to approve this factory to spend 1e5
/// USDC ($0.10) to be sent to the sponsorship vault during deployment. Assuming there is no additional
/// precision loss in the yield vault, a 1e5 yield buffer will cover the first 100k rounding errors on
/// deposits and withdraws and is not recoverable by the deployer.
///
/// If the yield buffer is depleted on a vault, the vault will prevent any further
/// deposits if it would result in a rounding error and any rounding errors incurred by withdrawals
/// will not be covered by yield. The yield buffer will be replenished automatically as yield accrues
/// on deposits.
/// @param _name Name of the ERC20 share minted by the vault
/// @param _symbol Symbol of the ERC20 share minted by the vault
/// @param _yieldVault Address of the ERC4626 vault in which assets are deposited to generate yield
/// @param _prizePool Address of the PrizePool that computes prizes
/// @param _claimer Address of the claimer
/// @param _yieldFeeRecipient Address of the yield fee recipient
/// @param _yieldFeePercentage Yield fee percentage
/// @param _yieldBuffer The size of the sponsorship vault yield buffer
/// @param _owner Address that will gain ownership of this contract
/// @param _contributionBeneficiary Address that will be sponsored with prize pool contributions.
/// @return _vault The newly deployed SponsorshipVault
function deployVault(
string memory _name,
string memory _symbol,
IERC4626 _yieldVault,
PrizePool _prizePool,
address _claimer,
address _yieldFeeRecipient,
uint32 _yieldFeePercentage,
uint256 _yieldBuffer,
address _owner,
address _contributionBeneficiary
) external returns (SponsorshipVault _vault) {
_vault = new SponsorshipVault{
salt: keccak256(abi.encode(msg.sender, deployerNonces[msg.sender]++))
}(
_name,
_symbol,
_yieldVault,
_prizePool,
_claimer,
_yieldFeeRecipient,
_yieldFeePercentage,
_yieldBuffer,
_owner,
_contributionBeneficiary
);

// A donation to fill the yield buffer is made to ensure that early depositors have
// rounding errors covered in the time before yield is actually generated.
if (_yieldBuffer > 0) {
IERC20(_vault.asset()).safeTransferFrom(msg.sender, address(_vault), _yieldBuffer);
}

allVaults.push(_vault);
deployedVaults[address(_vault)] = true;

emit NewSponsorshipVault(
_vault,
_yieldVault,
_prizePool,
_contributionBeneficiary,
_name,
_symbol
);
}

/// @notice Total number of vaults deployed by this factory.
/// @return uint256 Number of vaults deployed by this factory.
function totalVaults() external view returns (uint256) {
return allVaults.length;
}
}

0 comments on commit 1611381

Please sign in to comment.