Contract Name:
NegativeYieldToken
Contract Source Code:
File 1 of 1 : NegativeYieldToken
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.13;
// Source: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol + Claimable.sol
// Simplified by BoringCrypto
contract BoringOwnableData {
address public owner;
address public pendingOwner;
}
contract BoringOwnable is BoringOwnableData {
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/// @notice `owner` defaults to msg.sender on construction.
constructor() {
owner = msg.sender;
emit OwnershipTransferred(address(0), msg.sender);
}
/// @notice Transfers ownership to `newOwner`. Either directly or claimable by the new pending owner.
/// Can only be invoked by the current `owner`.
/// @param newOwner Address of the new owner.
/// @param direct True if `newOwner` should be set immediately. False if `newOwner` needs to use `claimOwnership`.
/// @param renounce Allows the `newOwner` to be `address(0)` if `direct` and `renounce` is True. Has no effect otherwise.
function transferOwnership(
address newOwner,
bool direct,
bool renounce
) public onlyOwner {
if (direct) {
// Checks
require(newOwner != address(0) || renounce, "Ownable: zero address");
// Effects
emit OwnershipTransferred(owner, newOwner);
owner = newOwner;
pendingOwner = address(0);
} else {
// Effects
pendingOwner = newOwner;
}
}
/// @notice Needs to be called by `pendingOwner` to claim ownership.
function claimOwnership() public {
address _pendingOwner = pendingOwner;
// Checks
require(msg.sender == _pendingOwner, "Ownable: caller != pending owner");
// Effects
emit OwnershipTransferred(owner, _pendingOwner);
owner = _pendingOwner;
pendingOwner = address(0);
}
/// @notice Only allows the `owner` to execute the function.
modifier onlyOwner() {
require(msg.sender == owner, "Ownable: caller is not the owner");
_;
}
}
/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
/*//////////////////////////////////////////////////////////////
METADATA STORAGE
//////////////////////////////////////////////////////////////*/
string public name;
string public symbol;
uint8 public immutable decimals;
/*//////////////////////////////////////////////////////////////
ERC20 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
/*//////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 internal immutable INITIAL_CHAIN_ID;
bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
mapping(address => uint256) public nonces;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
INITIAL_CHAIN_ID = block.chainid;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
}
/*//////////////////////////////////////////////////////////////
ERC20 LOGIC
//////////////////////////////////////////////////////////////*/
function approve(address spender, uint256 amount) public virtual returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transfer(address to, uint256 amount) public virtual returns (bool) {
balanceOf[msg.sender] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(msg.sender, to, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) public virtual returns (bool) {
uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
balanceOf[from] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(from, to, amount);
return true;
}
/*//////////////////////////////////////////////////////////////
EIP-2612 LOGIC
//////////////////////////////////////////////////////////////*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
// Unchecked because the only math done is incrementing
// the owner's nonce which cannot realistically overflow.
unchecked {
address recoveredAddress = ecrecover(
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256(
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
),
owner,
spender,
value,
nonces[owner]++,
deadline
)
)
)
),
v,
r,
s
);
require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
allowance[recoveredAddress][spender] = value;
}
emit Approval(owner, spender, value);
}
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
}
function computeDomainSeparator() internal view virtual returns (bytes32) {
return
keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(name)),
keccak256("1"),
block.chainid,
address(this)
)
);
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(address to, uint256 amount) internal virtual {
totalSupply += amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(address(0), to, amount);
}
function _burn(address from, uint256 amount) internal virtual {
balanceOf[from] -= amount;
// Cannot underflow because a user's balance
// will never be larger than the total supply.
unchecked {
totalSupply -= amount;
}
emit Transfer(from, address(0), amount);
}
}
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.
library SafeTransferLib {
event Debug(bool one, bool two, uint256 retsize);
/*//////////////////////////////////////////////////////////////
ETH OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferETH(address to, uint256 amount) internal {
bool success;
assembly {
// Transfer the ETH and store if it succeeded or not.
success := call(gas(), to, amount, 0, 0, 0, 0)
}
require(success, "ETH_TRANSFER_FAILED");
}
/*//////////////////////////////////////////////////////////////
ERC20 OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferFrom(
ERC20 token,
address from,
address to,
uint256 amount
) internal {
bool success;
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), from) // Append the "from" argument.
mstore(add(freeMemoryPointer, 36), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
)
}
require(success, "TRANSFER_FROM_FAILED");
}
function safeTransfer(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "TRANSFER_FAILED");
}
function safeApprove(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "APPROVE_FAILED");
}
}
/// @notice Arithmetic library with operations for fixed-point numbers.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/utils/FixedPointMathLib.sol)
/// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol)
library FixedPointMathLib {
/*//////////////////////////////////////////////////////////////
SIMPLIFIED FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/
uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s.
function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down.
}
function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up.
}
function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down.
}
function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up.
}
/*//////////////////////////////////////////////////////////////
LOW LEVEL FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/
function mulDivDown(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
assembly {
// Store x * y in z for now.
z := mul(x, y)
// Equivalent to require(denominator != 0 && (x == 0 || (x * y) / x == y))
if iszero(and(iszero(iszero(denominator)), or(iszero(x), eq(div(z, x), y)))) {
revert(0, 0)
}
// Divide z by the denominator.
z := div(z, denominator)
}
}
function mulDivUp(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
assembly {
// Store x * y in z for now.
z := mul(x, y)
// Equivalent to require(denominator != 0 && (x == 0 || (x * y) / x == y))
if iszero(and(iszero(iszero(denominator)), or(iszero(x), eq(div(z, x), y)))) {
revert(0, 0)
}
// First, divide z - 1 by the denominator and add 1.
// We allow z - 1 to underflow if z is 0, because we multiply the
// end result by 0 if z is zero, ensuring we return 0 if z is zero.
z := mul(iszero(iszero(z)), add(div(sub(z, 1), denominator), 1))
}
}
function rpow(
uint256 x,
uint256 n,
uint256 scalar
) internal pure returns (uint256 z) {
assembly {
switch x
case 0 {
switch n
case 0 {
// 0 ** 0 = 1
z := scalar
}
default {
// 0 ** n = 0
z := 0
}
}
default {
switch mod(n, 2)
case 0 {
// If n is even, store scalar in z for now.
z := scalar
}
default {
// If n is odd, store x in z for now.
z := x
}
// Shifting right by 1 is like dividing by 2.
let half := shr(1, scalar)
for {
// Shift n right by 1 before looping to halve it.
n := shr(1, n)
} n {
// Shift n right by 1 each iteration to halve it.
n := shr(1, n)
} {
// Revert immediately if x ** 2 would overflow.
// Equivalent to iszero(eq(div(xx, x), x)) here.
if shr(128, x) {
revert(0, 0)
}
// Store x squared.
let xx := mul(x, x)
// Round to the nearest number.
let xxRound := add(xx, half)
// Revert if xx + half overflowed.
if lt(xxRound, xx) {
revert(0, 0)
}
// Set x to scaled xxRound.
x := div(xxRound, scalar)
// If n is even:
if mod(n, 2) {
// Compute z * x.
let zx := mul(z, x)
// If z * x overflowed:
if iszero(eq(div(zx, x), z)) {
// Revert if x is non-zero.
if iszero(iszero(x)) {
revert(0, 0)
}
}
// Round to the nearest number.
let zxRound := add(zx, half)
// Revert if zx + half overflowed.
if lt(zxRound, zx) {
revert(0, 0)
}
// Return properly scaled zxRound.
z := div(zxRound, scalar)
}
}
}
}
}
/*//////////////////////////////////////////////////////////////
GENERAL NUMBER UTILITIES
//////////////////////////////////////////////////////////////*/
function sqrt(uint256 x) internal pure returns (uint256 z) {
assembly {
// Start off with z at 1.
z := 1
// Used below to help find a nearby power of 2.
let y := x
// Find the lowest power of 2 that is at least sqrt(x).
if iszero(lt(y, 0x100000000000000000000000000000000)) {
y := shr(128, y) // Like dividing by 2 ** 128.
z := shl(64, z) // Like multiplying by 2 ** 64.
}
if iszero(lt(y, 0x10000000000000000)) {
y := shr(64, y) // Like dividing by 2 ** 64.
z := shl(32, z) // Like multiplying by 2 ** 32.
}
if iszero(lt(y, 0x100000000)) {
y := shr(32, y) // Like dividing by 2 ** 32.
z := shl(16, z) // Like multiplying by 2 ** 16.
}
if iszero(lt(y, 0x10000)) {
y := shr(16, y) // Like dividing by 2 ** 16.
z := shl(8, z) // Like multiplying by 2 ** 8.
}
if iszero(lt(y, 0x100)) {
y := shr(8, y) // Like dividing by 2 ** 8.
z := shl(4, z) // Like multiplying by 2 ** 4.
}
if iszero(lt(y, 0x10)) {
y := shr(4, y) // Like dividing by 2 ** 4.
z := shl(2, z) // Like multiplying by 2 ** 2.
}
if iszero(lt(y, 0x8)) {
// Equivalent to 2 ** z.
z := shl(1, z)
}
// Shifting right by 1 is like dividing by 2.
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
// Compute a rounded down version of z.
let zRoundDown := div(x, z)
// If zRoundDown is smaller, use it.
if lt(zRoundDown, z) {
z := zRoundDown
}
}
}
}
/// @notice Minimal ERC4626 tokenized Vault implementation.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/mixins/ERC4626.sol)
abstract contract ERC4626 is ERC20 {
using SafeTransferLib for ERC20;
using FixedPointMathLib for uint256;
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares);
event Withdraw(
address indexed caller,
address indexed receiver,
address indexed owner,
uint256 assets,
uint256 shares
);
/*//////////////////////////////////////////////////////////////
IMMUTABLES
//////////////////////////////////////////////////////////////*/
ERC20 public immutable asset;
constructor(
ERC20 _asset,
string memory _name,
string memory _symbol
) ERC20(_name, _symbol, _asset.decimals()) {
asset = _asset;
}
/*//////////////////////////////////////////////////////////////
DEPOSIT/WITHDRAWAL LOGIC
//////////////////////////////////////////////////////////////*/
function deposit(uint256 assets, address receiver) public virtual returns (uint256 shares) {
// Check for rounding error since we round down in previewDeposit.
require((shares = previewDeposit(assets)) != 0, "ZERO_SHARES");
// Need to transfer before minting or ERC777s could reenter.
asset.safeTransferFrom(msg.sender, address(this), assets);
_mint(receiver, shares);
emit Deposit(msg.sender, receiver, assets, shares);
afterDeposit(assets, shares);
}
function mint(uint256 shares, address receiver) public virtual returns (uint256 assets) {
assets = previewMint(shares); // No need to check for rounding error, previewMint rounds up.
// Need to transfer before minting or ERC777s could reenter.
asset.safeTransferFrom(msg.sender, address(this), assets);
_mint(receiver, shares);
emit Deposit(msg.sender, receiver, assets, shares);
afterDeposit(assets, shares);
}
function withdraw(
uint256 assets,
address receiver,
address owner
) public virtual returns (uint256 shares) {
shares = previewWithdraw(assets); // No need to check for rounding error, previewWithdraw rounds up.
if (msg.sender != owner) {
uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares;
}
beforeWithdraw(assets, shares);
_burn(owner, shares);
emit Withdraw(msg.sender, receiver, owner, assets, shares);
asset.safeTransfer(receiver, assets);
}
function redeem(
uint256 shares,
address receiver,
address owner
) public virtual returns (uint256 assets) {
if (msg.sender != owner) {
uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares;
}
// Check for rounding error since we round down in previewRedeem.
require((assets = previewRedeem(shares)) != 0, "ZERO_ASSETS");
beforeWithdraw(assets, shares);
_burn(owner, shares);
emit Withdraw(msg.sender, receiver, owner, assets, shares);
asset.safeTransfer(receiver, assets);
}
/*//////////////////////////////////////////////////////////////
ACCOUNTING LOGIC
//////////////////////////////////////////////////////////////*/
function totalAssets() public view virtual returns (uint256);
function convertToShares(uint256 assets) public view virtual returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? assets : assets.mulDivDown(supply, totalAssets());
}
function convertToAssets(uint256 shares) public view virtual returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? shares : shares.mulDivDown(totalAssets(), supply);
}
function previewDeposit(uint256 assets) public view virtual returns (uint256) {
return convertToShares(assets);
}
function previewMint(uint256 shares) public view virtual returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? shares : shares.mulDivUp(totalAssets(), supply);
}
function previewWithdraw(uint256 assets) public view virtual returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? assets : assets.mulDivUp(supply, totalAssets());
}
function previewRedeem(uint256 shares) public view virtual returns (uint256) {
return convertToAssets(shares);
}
/*//////////////////////////////////////////////////////////////
DEPOSIT/WITHDRAWAL LIMIT LOGIC
//////////////////////////////////////////////////////////////*/
function maxDeposit(address) public view virtual returns (uint256) {
return type(uint256).max;
}
function maxMint(address) public view virtual returns (uint256) {
return type(uint256).max;
}
function maxWithdraw(address owner) public view virtual returns (uint256) {
return convertToAssets(balanceOf[owner]);
}
function maxRedeem(address owner) public view virtual returns (uint256) {
return balanceOf[owner];
}
/*//////////////////////////////////////////////////////////////
INTERNAL HOOKS LOGIC
//////////////////////////////////////////////////////////////*/
function beforeWithdraw(uint256 assets, uint256 shares) internal virtual {}
function afterDeposit(uint256 assets, uint256 shares) internal virtual {}
}
/// @notice Gas optimized reentrancy protection for smart contracts.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/utils/ReentrancyGuard.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol)
abstract contract ReentrancyGuard {
uint256 private locked = 1;
modifier nonReentrant() virtual {
require(locked == 1, "REENTRANCY");
locked = 2;
_;
locked = 1;
}
}
/// @notice Library for converting between addresses and bytes32 values.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/utils/Bytes32AddressLib.sol)
library Bytes32AddressLib {
function fromLast20Bytes(bytes32 bytesValue) internal pure returns (address) {
return address(uint160(uint256(bytesValue)));
}
function fillLast12Bytes(address addressValue) internal pure returns (bytes32) {
return bytes32(bytes20(addressValue));
}
}
/// @title BaseERC20
/// @author zefram.eth
/// @notice The base ERC20 contract used by NegativeYieldToken and PerpetualYieldToken
/// @dev Uses the same number of decimals as the vault's underlying token
contract BaseERC20 is ERC20 {
/// -----------------------------------------------------------------------
/// Errors
/// -----------------------------------------------------------------------
error Error_NotGate();
/// -----------------------------------------------------------------------
/// Immutable parameters
/// -----------------------------------------------------------------------
Gate public immutable gate;
address public immutable vault;
/// -----------------------------------------------------------------------
/// Constructor
/// -----------------------------------------------------------------------
constructor(
string memory name_,
string memory symbol_,
Gate gate_,
address vault_
) ERC20(name_, symbol_, gate_.getUnderlyingOfVault(vault_).decimals()) {
gate = gate_;
vault = vault_;
}
/// -----------------------------------------------------------------------
/// Gate-callable functions
/// -----------------------------------------------------------------------
function gateMint(address to, uint256 amount) external virtual {
if (msg.sender != address(gate)) {
revert Error_NotGate();
}
_mint(to, amount);
}
function gateBurn(address from, uint256 amount) external virtual {
if (msg.sender != address(gate)) {
revert Error_NotGate();
}
_burn(from, amount);
}
}
/// @title PerpetualYieldToken
/// @author zefram.eth
/// @notice The ERC20 contract representing perpetual yield tokens
contract PerpetualYieldToken is BaseERC20 {
/// -----------------------------------------------------------------------
/// Constructor
/// -----------------------------------------------------------------------
constructor(Gate gate_, address vault_)
BaseERC20(
gate_.perpetualYieldTokenName(vault_),
gate_.perpetualYieldTokenSymbol(vault_),
gate_,
vault_
)
{}
/// -----------------------------------------------------------------------
/// ERC20 overrides
/// -----------------------------------------------------------------------
function transfer(address to, uint256 amount)
public
virtual
override
returns (bool)
{
// load balances to save gas
uint256 fromBalance = balanceOf[msg.sender];
uint256 toBalance = balanceOf[to];
// call transfer hook
gate.beforePerpetualYieldTokenTransfer(
msg.sender,
to,
amount,
fromBalance,
toBalance
);
// do transfer
// skip during self transfers since toBalance is cached
// which leads to free minting, a critical issue
if (msg.sender != to) {
balanceOf[msg.sender] = fromBalance - amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] = toBalance + amount;
}
}
emit Transfer(msg.sender, to, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) public virtual override returns (bool) {
// load balances to save gas
uint256 fromBalance = balanceOf[from];
uint256 toBalance = balanceOf[to];
// call transfer hook
gate.beforePerpetualYieldTokenTransfer(
from,
to,
amount,
fromBalance,
toBalance
);
// update allowance
uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max)
allowance[from][msg.sender] = allowed - amount;
// do transfer
// skip during self transfers since toBalance is cached
// which leads to free minting, a critical issue
if (from != to) {
balanceOf[from] = fromBalance - amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] = toBalance + amount;
}
}
emit Transfer(from, to, amount);
return true;
}
}
contract Factory is BoringOwnable {
/// -----------------------------------------------------------------------
/// Library usage
/// -----------------------------------------------------------------------
using Bytes32AddressLib for address;
using Bytes32AddressLib for bytes32;
/// -----------------------------------------------------------------------
/// Errors
/// -----------------------------------------------------------------------
error Error_ProtocolFeeRecipientIsZero();
/// -----------------------------------------------------------------------
/// Events
/// -----------------------------------------------------------------------
event SetProtocolFee(ProtocolFeeInfo protocolFeeInfo_);
event DeployYieldTokenPair(
Gate indexed gate,
address indexed vault,
NegativeYieldToken nyt,
PerpetualYieldToken pyt
);
/// -----------------------------------------------------------------------
/// Storage variables
/// -----------------------------------------------------------------------
struct ProtocolFeeInfo {
uint8 fee; // each increment represents 0.1%, so max is 25.5%
address recipient;
}
/// @notice The protocol fee and the fee recipient address.
ProtocolFeeInfo public protocolFeeInfo;
/// -----------------------------------------------------------------------
/// Constructor
/// -----------------------------------------------------------------------
constructor(ProtocolFeeInfo memory protocolFeeInfo_) {
if (
protocolFeeInfo_.fee != 0 &&
protocolFeeInfo_.recipient == address(0)
) {
revert Error_ProtocolFeeRecipientIsZero();
}
protocolFeeInfo = protocolFeeInfo_;
emit SetProtocolFee(protocolFeeInfo_);
}
/// -----------------------------------------------------------------------
/// User actions
/// -----------------------------------------------------------------------
/// @notice Deploys the NegativeYieldToken and PerpetualYieldToken associated with a vault.
/// @dev Will revert if they have already been deployed.
/// @param gate The gate that will use the NYT and PYT
/// @param vault The vault to deploy NYT and PYT for
/// @return nyt The deployed NegativeYieldToken
/// @return pyt The deployed PerpetualYieldToken
function deployYieldTokenPair(Gate gate, address vault)
public
virtual
returns (NegativeYieldToken nyt, PerpetualYieldToken pyt)
{
// Use the CREATE2 opcode to deploy new NegativeYieldToken and PerpetualYieldToken contracts.
// This will revert if the contracts have already been deployed,
// as the salt & bytecode hash would be the same and we can't deploy with it twice.
nyt = new NegativeYieldToken{salt: bytes32(0)}(gate, vault);
pyt = new PerpetualYieldToken{salt: bytes32(0)}(gate, vault);
emit DeployYieldTokenPair(gate, vault, nyt, pyt);
}
/// -----------------------------------------------------------------------
/// Getters
/// -----------------------------------------------------------------------
/// @notice Returns the NegativeYieldToken associated with a gate & vault pair.
/// @dev Returns non-zero value even if the contract hasn't been deployed yet.
/// @param gate The gate to query
/// @param vault The vault to query
/// @return The NegativeYieldToken address
function getNegativeYieldToken(Gate gate, address vault)
public
view
virtual
returns (NegativeYieldToken)
{
return
NegativeYieldToken(_computeYieldTokenAddress(gate, vault, false));
}
/// @notice Returns the PerpetualYieldToken associated with a gate & vault pair.
/// @dev Returns non-zero value even if the contract hasn't been deployed yet.
/// @param gate The gate to query
/// @param vault The vault to query
/// @return The PerpetualYieldToken address
function getPerpetualYieldToken(Gate gate, address vault)
public
view
virtual
returns (PerpetualYieldToken)
{
return
PerpetualYieldToken(_computeYieldTokenAddress(gate, vault, true));
}
/// -----------------------------------------------------------------------
/// Owner functions
/// -----------------------------------------------------------------------
/// @notice Updates the protocol fee and/or the protocol fee recipient.
/// Only callable by the owner.
/// @param protocolFeeInfo_ The new protocol fee info
function ownerSetProtocolFee(ProtocolFeeInfo calldata protocolFeeInfo_)
external
virtual
onlyOwner
{
if (
protocolFeeInfo_.fee != 0 &&
protocolFeeInfo_.recipient == address(0)
) {
revert Error_ProtocolFeeRecipientIsZero();
}
protocolFeeInfo = protocolFeeInfo_;
emit SetProtocolFee(protocolFeeInfo_);
}
/// -----------------------------------------------------------------------
/// Internal utilities
/// -----------------------------------------------------------------------
/// @dev Computes the address of PYTs and NYTs using CREATE2.
function _computeYieldTokenAddress(
Gate gate,
address vault,
bool isPerpetualYieldToken
) internal view virtual returns (address) {
return
keccak256(
abi.encodePacked(
// Prefix:
bytes1(0xFF),
// Creator:
address(this),
// Salt:
bytes32(0),
// Bytecode hash:
keccak256(
abi.encodePacked(
// Deployment bytecode:
isPerpetualYieldToken
? type(PerpetualYieldToken).creationCode
: type(NegativeYieldToken).creationCode,
// Constructor arguments:
abi.encode(gate, vault)
)
)
)
).fromLast20Bytes(); // Convert the CREATE2 hash into an address.
}
}
abstract contract IxPYT is ERC4626 {
function sweep(address receiver) external virtual returns (uint256 shares);
}
/// @title Contains 512-bit math functions
/// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision
/// @dev Handles "phantom overflow" i.e., allows multiplication and division where an intermediate value overflows 256 bits
library FullMath {
/// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
/// @param a The multiplicand
/// @param b The multiplier
/// @param denominator The divisor
/// @return result The 256-bit result
/// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv
function mulDiv(
uint256 a,
uint256 b,
uint256 denominator
) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = a * b
// Compute the product mod 2**256 and mod 2**256 - 1
// then use the Chinese Remainder Theorem to reconstruct
// the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2**256 + prod0
uint256 prod0; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(a, b, not(0))
prod0 := mul(a, b)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division
if (prod1 == 0) {
require(denominator > 0);
assembly {
result := div(prod0, denominator)
}
return result;
}
// Make sure the result is less than 2**256.
// Also prevents denominator == 0
require(denominator > prod1);
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0]
// Compute remainder using mulmod
uint256 remainder;
assembly {
remainder := mulmod(a, b, denominator)
}
// Subtract 256 bit number from 512 bit number
assembly {
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator
// Compute largest power of two divisor of denominator.
// Always >= 1.
uint256 twos = (0 - denominator) & denominator;
// Divide denominator by power of two
assembly {
denominator := div(denominator, twos)
}
// Divide [prod1 prod0] by the factors of two
assembly {
prod0 := div(prod0, twos)
}
// Shift in bits from prod1 into prod0. For this we need
// to flip `twos` such that it is 2**256 / twos.
// If twos is zero, then it becomes one
assembly {
twos := add(div(sub(0, twos), twos), 1)
}
prod0 |= prod1 * twos;
// Invert denominator mod 2**256
// Now that denominator is an odd number, it has an inverse
// modulo 2**256 such that denominator * inv = 1 mod 2**256.
// Compute the inverse by starting with a seed that is correct
// correct for four bits. That is, denominator * inv = 1 mod 2**4
uint256 inv = (3 * denominator) ^ 2;
// Now use Newton-Raphson iteration to improve the precision.
// Thanks to Hensel's lifting lemma, this also works in modular
// arithmetic, doubling the correct bits in each step.
inv *= 2 - denominator * inv; // inverse mod 2**8
inv *= 2 - denominator * inv; // inverse mod 2**16
inv *= 2 - denominator * inv; // inverse mod 2**32
inv *= 2 - denominator * inv; // inverse mod 2**64
inv *= 2 - denominator * inv; // inverse mod 2**128
inv *= 2 - denominator * inv; // inverse mod 2**256
// Because the division is now exact we can divide by multiplying
// with the modular inverse of denominator. This will give us the
// correct result modulo 2**256. Since the precoditions guarantee
// that the outcome is less than 2**256, this is the final result.
// We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inv;
return result;
}
}
/// @notice Calculates ceil(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
/// @param a The multiplicand
/// @param b The multiplier
/// @param denominator The divisor
/// @return result The 256-bit result
function mulDivRoundingUp(
uint256 a,
uint256 b,
uint256 denominator
) internal pure returns (uint256 result) {
unchecked {
result = mulDiv(a, b, denominator);
if (mulmod(a, b, denominator) > 0) {
require(result < type(uint256).max);
result++;
}
}
}
}
/// @title Multicall
/// @notice Enables calling multiple methods in a single call to the contract
abstract contract Multicall {
function multicall(bytes[] calldata data)
external
payable
returns (bytes[] memory results)
{
results = new bytes[](data.length);
for (uint256 i = 0; i < data.length; i++) {
(bool success, bytes memory result) = address(this).delegatecall(
data[i]
);
if (!success) {
// Next 5 lines from https://ethereum.stackexchange.com/a/83577
if (result.length < 68) revert();
assembly {
result := add(result, 0x04)
}
revert(abi.decode(result, (string)));
}
results[i] = result;
}
}
}
/// @title Self Permit
/// @notice Functionality to call permit on any EIP-2612-compliant token for use in the route
/// @dev These functions are expected to be embedded in multicalls to allow EOAs to approve a contract and call a function
/// that requires an approval in a single transaction.
abstract contract SelfPermit {
function selfPermit(
ERC20 token,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public payable {
token.permit(msg.sender, address(this), value, deadline, v, r, s);
}
function selfPermitIfNecessary(
ERC20 token,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external payable {
if (token.allowance(msg.sender, address(this)) < value)
selfPermit(token, value, deadline, v, r, s);
}
}
/// @title Gate
/// @author zefram.eth
/// @notice Gate is the main contract users interact with to mint/burn NegativeYieldToken
/// and PerpetualYieldToken, as well as claim the yield earned by PYTs.
/// @dev Gate is an abstract contract that should be inherited from in order to support
/// a specific vault protocol (e.g. YearnGate supports YearnVault). Each Gate handles
/// all vaults & associated NYTs/PYTs of a specific vault protocol.
///
/// Vaults are yield-generating contracts used by Gate. Gate makes several assumptions about
/// a vault:
/// 1) A vault has a single associated underlying token that is immutable.
/// 2) A vault gives depositors yield denominated in the underlying token.
/// 3) A vault depositor owns shares in the vault, which represents their deposit.
/// 4) Vaults have a notion of "price per share", which is the amount of underlying tokens
/// each vault share can be redeemed for.
/// 5) If vault shares are represented using an ERC20 token, then the ERC20 token contract must be
/// the vault contract itself.
abstract contract Gate is
ReentrancyGuard,
Multicall,
SelfPermit,
BoringOwnable
{
/// -----------------------------------------------------------------------
/// Library usage
/// -----------------------------------------------------------------------
using SafeTransferLib for ERC20;
using SafeTransferLib for ERC4626;
/// -----------------------------------------------------------------------
/// Errors
/// -----------------------------------------------------------------------
error Error_InvalidInput();
error Error_VaultSharesNotERC20();
error Error_TokenPairNotDeployed();
error Error_EmergencyExitNotActivated();
error Error_SenderNotPerpetualYieldToken();
error Error_EmergencyExitAlreadyActivated();
/// -----------------------------------------------------------------------
/// Events
/// -----------------------------------------------------------------------
event EnterWithUnderlying(
address sender,
address indexed nytRecipient,
address indexed pytRecipient,
address indexed vault,
IxPYT xPYT,
uint256 underlyingAmount
);
event EnterWithVaultShares(
address sender,
address indexed nytRecipient,
address indexed pytRecipient,
address indexed vault,
IxPYT xPYT,
uint256 vaultSharesAmount
);
event ExitToUnderlying(
address indexed sender,
address indexed recipient,
address indexed vault,
IxPYT xPYT,
uint256 underlyingAmount
);
event ExitToVaultShares(
address indexed sender,
address indexed recipient,
address indexed vault,
IxPYT xPYT,
uint256 vaultSharesAmount
);
event ClaimYieldInUnderlying(
address indexed sender,
address indexed recipient,
address indexed vault,
uint256 underlyingAmount
);
event ClaimYieldInVaultShares(
address indexed sender,
address indexed recipient,
address indexed vault,
uint256 vaultSharesAmount
);
event ClaimYieldAndEnter(
address sender,
address indexed nytRecipient,
address indexed pytRecipient,
address indexed vault,
IxPYT xPYT,
uint256 amount
);
/// -----------------------------------------------------------------------
/// Structs
/// -----------------------------------------------------------------------
/// @param activated True if emergency exit has been activated, false if not
/// @param pytPriceInUnderlying The amount of underlying assets each PYT can redeem for.
/// Should be a value in the range [0, PRECISION]
struct EmergencyExitStatus {
bool activated;
uint96 pytPriceInUnderlying;
}
/// -----------------------------------------------------------------------
/// Constants
/// -----------------------------------------------------------------------
/// @notice The decimals of precision used by yieldPerTokenStored and pricePerVaultShareStored
uint256 internal constant PRECISION_DECIMALS = 27;
/// @notice The precision used by yieldPerTokenStored and pricePerVaultShareStored
uint256 internal constant PRECISION = 10**PRECISION_DECIMALS;
/// -----------------------------------------------------------------------
/// Immutable parameters
/// -----------------------------------------------------------------------
Factory public immutable factory;
/// -----------------------------------------------------------------------
/// Storage variables
/// -----------------------------------------------------------------------
/// @notice The amount of underlying tokens each vault share is worth, at the time of the last update.
/// Uses PRECISION.
/// @dev vault => value
mapping(address => uint256) public pricePerVaultShareStored;
/// @notice The amount of yield each PYT has accrued, at the time of the last update.
/// Scaled by PRECISION.
/// @dev vault => value
mapping(address => uint256) public yieldPerTokenStored;
/// @notice The amount of yield each PYT has accrued, at the time when a user has last interacted
/// with the gate/PYT. Shifted by 1, so e.g. 3 represents 2, 10 represents 9.
/// @dev vault => user => value
/// The value is shifted to use 0 for representing uninitialized users.
mapping(address => mapping(address => uint256))
public userYieldPerTokenStored;
/// @notice The amount of yield a user has accrued, at the time when they last interacted
/// with the gate/PYT (without calling claimYieldInUnderlying()).
/// Shifted by 1, so e.g. 3 represents 2, 10 represents 9.
/// @dev vault => user => value
mapping(address => mapping(address => uint256)) public userAccruedYield;
/// @notice Stores info relevant to emergency exits of a vault.
/// @dev vault => value
mapping(address => EmergencyExitStatus) public emergencyExitStatusOfVault;
/// -----------------------------------------------------------------------
/// Initialization
/// -----------------------------------------------------------------------
constructor(Factory factory_) {
factory = factory_;
}
/// -----------------------------------------------------------------------
/// User actions
/// -----------------------------------------------------------------------
/// @notice Converts underlying tokens into NegativeYieldToken and PerpetualYieldToken.
/// The amount of NYT and PYT minted will be equal to the underlying token amount.
/// @dev The underlying tokens will be immediately deposited into the specified vault.
/// If the NYT and PYT for the specified vault haven't been deployed yet, this call will
/// deploy them before proceeding, which will increase the gas cost significantly.
/// @param nytRecipient The recipient of the minted NYT
/// @param pytRecipient The recipient of the minted PYT
/// @param vault The vault to mint NYT and PYT for
/// @param xPYT The xPYT contract to deposit the minted PYT into. Set to 0 to receive raw PYT instead.
/// @param underlyingAmount The amount of underlying tokens to use
/// @return mintAmount The amount of NYT and PYT minted (the amounts are equal)
function enterWithUnderlying(
address nytRecipient,
address pytRecipient,
address vault,
IxPYT xPYT,
uint256 underlyingAmount
) external virtual nonReentrant returns (uint256 mintAmount) {
/// -----------------------------------------------------------------------
/// Validation
/// -----------------------------------------------------------------------
if (underlyingAmount == 0) {
return 0;
}
/// -----------------------------------------------------------------------
/// State updates & effects
/// -----------------------------------------------------------------------
// mint PYT and NYT
mintAmount = underlyingAmount;
_enter(
nytRecipient,
pytRecipient,
vault,
xPYT,
underlyingAmount,
getPricePerVaultShare(vault)
);
// transfer underlying from msg.sender to address(this)
ERC20 underlying = getUnderlyingOfVault(vault);
underlying.safeTransferFrom(
msg.sender,
address(this),
underlyingAmount
);
// deposit underlying into vault
_depositIntoVault(underlying, underlyingAmount, vault);
emit EnterWithUnderlying(
msg.sender,
nytRecipient,
pytRecipient,
vault,
xPYT,
underlyingAmount
);
}
/// @notice Converts vault share tokens into NegativeYieldToken and PerpetualYieldToken.
/// @dev Only available if vault shares are transferrable ERC20 tokens.
/// If the NYT and PYT for the specified vault haven't been deployed yet, this call will
/// deploy them before proceeding, which will increase the gas cost significantly.
/// @param nytRecipient The recipient of the minted NYT
/// @param pytRecipient The recipient of the minted PYT
/// @param vault The vault to mint NYT and PYT for
/// @param xPYT The xPYT contract to deposit the minted PYT into. Set to 0 to receive raw PYT instead.
/// @param vaultSharesAmount The amount of vault share tokens to use
/// @return mintAmount The amount of NYT and PYT minted (the amounts are equal)
function enterWithVaultShares(
address nytRecipient,
address pytRecipient,
address vault,
IxPYT xPYT,
uint256 vaultSharesAmount
) external virtual nonReentrant returns (uint256 mintAmount) {
/// -----------------------------------------------------------------------
/// Validation
/// -----------------------------------------------------------------------
if (vaultSharesAmount == 0) {
return 0;
}
// only supported if vault shares are ERC20
if (!vaultSharesIsERC20()) {
revert Error_VaultSharesNotERC20();
}
/// -----------------------------------------------------------------------
/// State updates & effects
/// -----------------------------------------------------------------------
// mint PYT and NYT
uint256 updatedPricePerVaultShare = getPricePerVaultShare(vault);
mintAmount = _vaultSharesAmountToUnderlyingAmount(
vault,
vaultSharesAmount,
updatedPricePerVaultShare
);
_enter(
nytRecipient,
pytRecipient,
vault,
xPYT,
mintAmount,
updatedPricePerVaultShare
);
// transfer vault tokens from msg.sender to address(this)
ERC20(vault).safeTransferFrom(
msg.sender,
address(this),
vaultSharesAmount
);
emit EnterWithVaultShares(
msg.sender,
nytRecipient,
pytRecipient,
vault,
xPYT,
vaultSharesAmount
);
}
/// @notice Converts NegativeYieldToken and PerpetualYieldToken to underlying tokens.
/// The amount of NYT and PYT burned will be equal to the underlying token amount.
/// @dev The underlying tokens will be immediately withdrawn from the specified vault.
/// If the NYT and PYT for the specified vault haven't been deployed yet, this call will
/// revert.
/// @param recipient The recipient of the minted NYT and PYT
/// @param vault The vault to mint NYT and PYT for
/// @param xPYT The xPYT contract to use for burning PYT. Set to 0 to burn raw PYT instead.
/// @param underlyingAmount The amount of underlying tokens requested
/// @return burnAmount The amount of NYT and PYT burned (the amounts are equal)
function exitToUnderlying(
address recipient,
address vault,
IxPYT xPYT,
uint256 underlyingAmount
) external virtual nonReentrant returns (uint256 burnAmount) {
/// -----------------------------------------------------------------------
/// Validation
/// -----------------------------------------------------------------------
if (underlyingAmount == 0) {
return 0;
}
/// -----------------------------------------------------------------------
/// State updates & effects
/// -----------------------------------------------------------------------
// burn PYT and NYT
uint256 updatedPricePerVaultShare = getPricePerVaultShare(vault);
burnAmount = underlyingAmount;
_exit(vault, xPYT, underlyingAmount, updatedPricePerVaultShare);
// withdraw underlying from vault to recipient
// don't check balance since user can just withdraw slightly less
// saves gas this way
underlyingAmount = _withdrawFromVault(
recipient,
vault,
underlyingAmount,
updatedPricePerVaultShare,
false
);
emit ExitToUnderlying(
msg.sender,
recipient,
vault,
xPYT,
underlyingAmount
);
}
/// @notice Converts NegativeYieldToken and PerpetualYieldToken to vault share tokens.
/// The amount of NYT and PYT burned will be equal to the underlying token amount.
/// @dev Only available if vault shares are transferrable ERC20 tokens.
/// If the NYT and PYT for the specified vault haven't been deployed yet, this call will
/// revert.
/// @param recipient The recipient of the minted NYT and PYT
/// @param vault The vault to mint NYT and PYT for
/// @param xPYT The xPYT contract to use for burning PYT. Set to 0 to burn raw PYT instead.
/// @param vaultSharesAmount The amount of vault share tokens requested
/// @return burnAmount The amount of NYT and PYT burned (the amounts are equal)
function exitToVaultShares(
address recipient,
address vault,
IxPYT xPYT,
uint256 vaultSharesAmount
) external virtual nonReentrant returns (uint256 burnAmount) {
/// -----------------------------------------------------------------------
/// Validation
/// -----------------------------------------------------------------------
if (vaultSharesAmount == 0) {
return 0;
}
// only supported if vault shares are ERC20
if (!vaultSharesIsERC20()) {
revert Error_VaultSharesNotERC20();
}
/// -----------------------------------------------------------------------
/// State updates & effects
/// -----------------------------------------------------------------------
// burn PYT and NYT
uint256 updatedPricePerVaultShare = getPricePerVaultShare(vault);
burnAmount = _vaultSharesAmountToUnderlyingAmountRoundingUp(
vault,
vaultSharesAmount,
updatedPricePerVaultShare
);
_exit(vault, xPYT, burnAmount, updatedPricePerVaultShare);
// transfer vault tokens to recipient
ERC20(vault).safeTransfer(recipient, vaultSharesAmount);
emit ExitToVaultShares(
msg.sender,
recipient,
vault,
xPYT,
vaultSharesAmount
);
}
/// @notice Claims the yield earned by the PerpetualYieldToken balance of msg.sender, in the underlying token.
/// @dev If the NYT and PYT for the specified vault haven't been deployed yet, this call will
/// revert.
/// @param recipient The recipient of the yield
/// @param vault The vault to claim yield from
/// @return yieldAmount The amount of yield claimed, in underlying tokens
function claimYieldInUnderlying(address recipient, address vault)
external
virtual
nonReentrant
returns (uint256 yieldAmount)
{
/// -----------------------------------------------------------------------
/// State updates
/// -----------------------------------------------------------------------
// update storage variables and compute yield amount
uint256 updatedPricePerVaultShare = getPricePerVaultShare(vault);
yieldAmount = _claimYield(vault, updatedPricePerVaultShare);
// withdraw yield
if (yieldAmount != 0) {
/// -----------------------------------------------------------------------
/// Effects
/// -----------------------------------------------------------------------
(uint8 fee, address protocolFeeRecipient) = factory
.protocolFeeInfo();
if (fee != 0) {
uint256 protocolFee = (yieldAmount * fee) / 1000;
unchecked {
// can't underflow since fee < 256
yieldAmount -= protocolFee;
}
if (vaultSharesIsERC20()) {
// vault shares are in ERC20
// do share transfer
protocolFee = _underlyingAmountToVaultSharesAmount(
vault,
protocolFee,
updatedPricePerVaultShare
);
uint256 vaultSharesBalance = ERC20(vault).balanceOf(
address(this)
);
if (protocolFee > vaultSharesBalance) {
protocolFee = vaultSharesBalance;
}
if (protocolFee != 0) {
ERC20(vault).safeTransfer(
protocolFeeRecipient,
protocolFee
);
}
} else {
// vault shares are not in ERC20
// withdraw underlying from vault
// checkBalance is set to true to prevent getting stuck
// due to rounding errors
if (protocolFee != 0) {
_withdrawFromVault(
protocolFeeRecipient,
vault,
protocolFee,
updatedPricePerVaultShare,
true
);
}
}
}
// withdraw underlying to recipient
// checkBalance is set to true to prevent getting stuck
// due to rounding errors
yieldAmount = _withdrawFromVault(
recipient,
vault,
yieldAmount,
updatedPricePerVaultShare,
true
);
emit ClaimYieldInUnderlying(
msg.sender,
recipient,
vault,
yieldAmount
);
}
}
/// @notice Claims the yield earned by the PerpetualYieldToken balance of msg.sender, in vault shares.
/// @dev Only available if vault shares are transferrable ERC20 tokens.
/// If the NYT and PYT for the specified vault haven't been deployed yet, this call will
/// revert.
/// @param recipient The recipient of the yield
/// @param vault The vault to claim yield from
/// @return yieldAmount The amount of yield claimed, in vault shares
function claimYieldInVaultShares(address recipient, address vault)
external
virtual
nonReentrant
returns (uint256 yieldAmount)
{
/// -----------------------------------------------------------------------
/// Validation
/// -----------------------------------------------------------------------
// only supported if vault shares are ERC20
if (!vaultSharesIsERC20()) {
revert Error_VaultSharesNotERC20();
}
/// -----------------------------------------------------------------------
/// State updates
/// -----------------------------------------------------------------------
// update storage variables and compute yield amount
uint256 updatedPricePerVaultShare = getPricePerVaultShare(vault);
yieldAmount = _claimYield(vault, updatedPricePerVaultShare);
// withdraw yield
if (yieldAmount != 0) {
/// -----------------------------------------------------------------------
/// Effects
/// -----------------------------------------------------------------------
// convert yieldAmount to be denominated in vault shares
yieldAmount = _underlyingAmountToVaultSharesAmount(
vault,
yieldAmount,
updatedPricePerVaultShare
);
(uint8 fee, address protocolFeeRecipient) = factory
.protocolFeeInfo();
uint256 vaultSharesBalance = getVaultShareBalance(vault);
if (fee != 0) {
uint256 protocolFee = (yieldAmount * fee) / 1000;
protocolFee = protocolFee > vaultSharesBalance
? vaultSharesBalance
: protocolFee;
unchecked {
// can't underflow since fee < 256
yieldAmount -= protocolFee;
}
if (protocolFee > 0) {
ERC20(vault).safeTransfer(
protocolFeeRecipient,
protocolFee
);
vaultSharesBalance -= protocolFee;
}
}
// transfer vault shares to recipient
// check if vault shares is enough to prevent getting stuck
// from rounding errors
yieldAmount = yieldAmount > vaultSharesBalance
? vaultSharesBalance
: yieldAmount;
if (yieldAmount > 0) {
ERC20(vault).safeTransfer(recipient, yieldAmount);
}
emit ClaimYieldInVaultShares(
msg.sender,
recipient,
vault,
yieldAmount
);
}
}
/// @notice Claims the yield earned by the PerpetualYieldToken balance of msg.sender, and immediately
/// use the yield to mint NYT and PYT.
/// @dev Introduced to save gas for xPYT compounding, since it avoids vault withdraws/transfers.
/// If the NYT and PYT for the specified vault haven't been deployed yet, this call will
/// revert.
/// @param nytRecipient The recipient of the minted NYT
/// @param pytRecipient The recipient of the minted PYT
/// @param vault The vault to claim yield from
/// @param xPYT The xPYT contract to deposit the minted PYT into. Set to 0 to receive raw PYT instead.
/// @return yieldAmount The amount of yield claimed, in underlying tokens
function claimYieldAndEnter(
address nytRecipient,
address pytRecipient,
address vault,
IxPYT xPYT
) external virtual nonReentrant returns (uint256 yieldAmount) {
// update storage variables and compute yield amount
uint256 updatedPricePerVaultShare = getPricePerVaultShare(vault);
yieldAmount = _claimYield(vault, updatedPricePerVaultShare);
// use yield to mint NYT and PYT
if (yieldAmount != 0) {
(uint8 fee, address protocolFeeRecipient) = factory
.protocolFeeInfo();
if (fee != 0) {
uint256 protocolFee = (yieldAmount * fee) / 1000;
unchecked {
// can't underflow since fee < 256
yieldAmount -= protocolFee;
}
if (vaultSharesIsERC20()) {
// vault shares are in ERC20
// do share transfer
protocolFee = _underlyingAmountToVaultSharesAmount(
vault,
protocolFee,
updatedPricePerVaultShare
);
uint256 vaultSharesBalance = ERC20(vault).balanceOf(
address(this)
);
if (protocolFee > vaultSharesBalance) {
protocolFee = vaultSharesBalance;
}
if (protocolFee != 0) {
ERC20(vault).safeTransfer(
protocolFeeRecipient,
protocolFee
);
}
} else {
// vault shares are not in ERC20
// withdraw underlying from vault
// checkBalance is set to true to prevent getting stuck
// due to rounding errors
if (protocolFee != 0) {
_withdrawFromVault(
protocolFeeRecipient,
vault,
protocolFee,
updatedPricePerVaultShare,
true
);
}
}
}
NegativeYieldToken nyt = getNegativeYieldTokenForVault(vault);
PerpetualYieldToken pyt = getPerpetualYieldTokenForVault(vault);
if (address(xPYT) == address(0)) {
// accrue yield to pytRecipient if they're not msg.sender
// no need to do it if the recipient is msg.sender, since
// we already accrued yield in _claimYield
if (pytRecipient != msg.sender) {
_accrueYield(
vault,
pyt,
pytRecipient,
updatedPricePerVaultShare
);
}
} else {
// accrue yield to xPYT contract since it gets minted PYT
_accrueYield(
vault,
pyt,
address(xPYT),
updatedPricePerVaultShare
);
}
// mint NYTs and PYTs
nyt.gateMint(nytRecipient, yieldAmount);
if (address(xPYT) == address(0)) {
// mint raw PYT to recipient
pyt.gateMint(pytRecipient, yieldAmount);
} else {
// mint PYT to xPYT contract
pyt.gateMint(address(xPYT), yieldAmount);
/// -----------------------------------------------------------------------
/// Effects
/// -----------------------------------------------------------------------
// call sweep to mint xPYT using the PYT
xPYT.sweep(pytRecipient);
}
emit ClaimYieldAndEnter(
msg.sender,
nytRecipient,
pytRecipient,
vault,
xPYT,
yieldAmount
);
}
}
/// -----------------------------------------------------------------------
/// Getters
/// -----------------------------------------------------------------------
/// @notice Returns the NegativeYieldToken associated with a vault.
/// @dev Returns non-zero value even if the contract hasn't been deployed yet.
/// @param vault The vault to query
/// @return The NegativeYieldToken address
function getNegativeYieldTokenForVault(address vault)
public
view
virtual
returns (NegativeYieldToken)
{
return factory.getNegativeYieldToken(this, vault);
}
/// @notice Returns the PerpetualYieldToken associated with a vault.
/// @dev Returns non-zero value even if the contract hasn't been deployed yet.
/// @param vault The vault to query
/// @return The PerpetualYieldToken address
function getPerpetualYieldTokenForVault(address vault)
public
view
virtual
returns (PerpetualYieldToken)
{
return factory.getPerpetualYieldToken(this, vault);
}
/// @notice Returns the amount of yield claimable by a PerpetualYieldToken holder from a vault.
/// Accounts for protocol fees.
/// @param vault The vault to query
/// @param user The PYT holder to query
/// @return yieldAmount The amount of yield claimable
function getClaimableYieldAmount(address vault, address user)
external
view
virtual
returns (uint256 yieldAmount)
{
PerpetualYieldToken pyt = getPerpetualYieldTokenForVault(vault);
uint256 userYieldPerTokenStored_ = userYieldPerTokenStored[vault][user];
if (userYieldPerTokenStored_ == 0) {
// uninitialized account
return 0;
}
yieldAmount = _getClaimableYieldAmount(
vault,
user,
_computeYieldPerToken(vault, pyt, getPricePerVaultShare(vault)),
userYieldPerTokenStored_,
pyt.balanceOf(user)
);
(uint8 fee, ) = factory.protocolFeeInfo();
if (fee != 0) {
uint256 protocolFee = (yieldAmount * fee) / 1000;
unchecked {
// can't underflow since fee < 256
yieldAmount -= protocolFee;
}
}
}
/// @notice Computes the latest yieldPerToken value for a vault.
/// @param vault The vault to query
/// @return The latest yieldPerToken value
function computeYieldPerToken(address vault)
external
view
virtual
returns (uint256)
{
return
_computeYieldPerToken(
vault,
getPerpetualYieldTokenForVault(vault),
getPricePerVaultShare(vault)
);
}
/// @notice Returns the underlying token of a vault.
/// @param vault The vault to query
/// @return The underlying token
function getUnderlyingOfVault(address vault)
public
view
virtual
returns (ERC20);
/// @notice Returns the amount of underlying tokens each share of a vault is worth.
/// @param vault The vault to query
/// @return The pricePerVaultShare value
function getPricePerVaultShare(address vault)
public
view
virtual
returns (uint256);
/// @notice Returns the amount of vault shares owned by the gate.
/// @param vault The vault to query
/// @return The gate's vault share balance
function getVaultShareBalance(address vault)
public
view
virtual
returns (uint256);
/// @return True if the vaults supported by this gate use transferrable ERC20 tokens
/// to represent shares, false otherwise.
function vaultSharesIsERC20() public pure virtual returns (bool);
/// @notice Computes the ERC20 name of the NegativeYieldToken of a vault.
/// @param vault The vault to query
/// @return The ERC20 name
function negativeYieldTokenName(address vault)
external
view
virtual
returns (string memory);
/// @notice Computes the ERC20 symbol of the NegativeYieldToken of a vault.
/// @param vault The vault to query
/// @return The ERC20 symbol
function negativeYieldTokenSymbol(address vault)
external
view
virtual
returns (string memory);
/// @notice Computes the ERC20 name of the PerpetualYieldToken of a vault.
/// @param vault The vault to query
/// @return The ERC20 name
function perpetualYieldTokenName(address vault)
external
view
virtual
returns (string memory);
/// @notice Computes the ERC20 symbol of the NegativeYieldToken of a vault.
/// @param vault The vault to query
/// @return The ERC20 symbol
function perpetualYieldTokenSymbol(address vault)
external
view
virtual
returns (string memory);
/// -----------------------------------------------------------------------
/// PYT transfer hook
/// -----------------------------------------------------------------------
/// @notice SHOULD NOT BE CALLED BY USERS, ONLY CALLED BY PERPETUAL YIELD TOKEN CONTRACTS
/// @dev Called by PYT contracts deployed by this gate before each token transfer, in order to
/// accrue the yield earned by the from & to accounts
/// @param from The token transfer from account
/// @param to The token transfer to account
/// @param fromBalance The token balance of the from account before the transfer
/// @param toBalance The token balance of the to account before the transfer
function beforePerpetualYieldTokenTransfer(
address from,
address to,
uint256 amount,
uint256 fromBalance,
uint256 toBalance
) external virtual {
/// -----------------------------------------------------------------------
/// Validation
/// -----------------------------------------------------------------------
if (amount == 0) {
return;
}
address vault = PerpetualYieldToken(msg.sender).vault();
PerpetualYieldToken pyt = getPerpetualYieldTokenForVault(vault);
if (msg.sender != address(pyt)) {
revert Error_SenderNotPerpetualYieldToken();
}
/// -----------------------------------------------------------------------
/// State updates
/// -----------------------------------------------------------------------
// accrue yield
uint256 updatedPricePerVaultShare = getPricePerVaultShare(vault);
uint256 updatedYieldPerToken = _computeYieldPerToken(
vault,
pyt,
updatedPricePerVaultShare
);
yieldPerTokenStored[vault] = updatedYieldPerToken;
pricePerVaultShareStored[vault] = updatedPricePerVaultShare;
// we know the from account must have held PYTs before
// so we will always accrue the yield earned by the from account
userAccruedYield[vault][from] =
_getClaimableYieldAmount(
vault,
from,
updatedYieldPerToken,
userYieldPerTokenStored[vault][from],
fromBalance
) +
1;
userYieldPerTokenStored[vault][from] = updatedYieldPerToken + 1;
// the to account might not have held PYTs before
// we only accrue yield if they have
uint256 toUserYieldPerTokenStored = userYieldPerTokenStored[vault][to];
if (toUserYieldPerTokenStored != 0) {
// to account has held PYTs before
userAccruedYield[vault][to] =
_getClaimableYieldAmount(
vault,
to,
updatedYieldPerToken,
toUserYieldPerTokenStored,
toBalance
) +
1;
}
userYieldPerTokenStored[vault][to] = updatedYieldPerToken + 1;
}
/// -----------------------------------------------------------------------
/// Emergency exit
/// -----------------------------------------------------------------------
/// @notice Activates the emergency exit mode for a certain vault. Only callable by owner.
/// @dev Activating emergency exit allows PYT/NYT holders to do single-sided burns to redeem the underlying
/// collateral. This is to prevent cases where a large portion of PYT/NYT is locked up in a buggy/malicious contract
/// and locks up the underlying collateral forever.
/// @param vault The vault to activate emergency exit for
/// @param pytPriceInUnderlying The amount of underlying asset burning each PYT can redeem. Scaled by PRECISION.
function ownerActivateEmergencyExitForVault(
address vault,
uint96 pytPriceInUnderlying
) external virtual onlyOwner {
/// -----------------------------------------------------------------------
/// Validation
/// -----------------------------------------------------------------------
// we only allow emergency exit to be activated once (until deactivation)
// because if pytPriceInUnderlying is ever modified after activation
// then PYT/NYT will become unbacked
if (emergencyExitStatusOfVault[vault].activated) {
revert Error_EmergencyExitAlreadyActivated();
}
// we need to ensure the PYT price value is within the range [0, PRECISION]
if (pytPriceInUnderlying > PRECISION) {
revert Error_InvalidInput();
}
// the PYT & NYT must have already been deployed
NegativeYieldToken nyt = getNegativeYieldTokenForVault(vault);
if (address(nyt).code.length == 0) {
revert Error_TokenPairNotDeployed();
}
/// -----------------------------------------------------------------------
/// State updates
/// -----------------------------------------------------------------------
emergencyExitStatusOfVault[vault] = EmergencyExitStatus({
activated: true,
pytPriceInUnderlying: pytPriceInUnderlying
});
}
/// @notice Deactivates the emergency exit mode for a certain vault. Only callable by owner.
/// @param vault The vault to deactivate emergency exit for
function ownerDeactivateEmergencyExitForVault(address vault)
external
virtual
onlyOwner
{
/// -----------------------------------------------------------------------
/// Validation
/// -----------------------------------------------------------------------
// can only deactivate emergency exit when it's already activated
if (!emergencyExitStatusOfVault[vault].activated) {
revert Error_EmergencyExitNotActivated();
}
/// -----------------------------------------------------------------------
/// State updates
/// -----------------------------------------------------------------------
// reset the emergency exit status
delete emergencyExitStatusOfVault[vault];
}
/// @notice Emergency exit NYTs into the underlying asset. Only callable when emergency exit has
/// been activated for the vault.
/// @param vault The vault to exit NYT for
/// @param amount The amount of NYT to exit
/// @param recipient The recipient of the underlying asset
/// @return underlyingAmount The amount of underlying asset exited
function emergencyExitNegativeYieldToken(
address vault,
uint256 amount,
address recipient
) external virtual returns (uint256 underlyingAmount) {
/// -----------------------------------------------------------------------
/// Validation
/// -----------------------------------------------------------------------
// ensure emergency exit is active
EmergencyExitStatus memory status = emergencyExitStatusOfVault[vault];
if (!status.activated) {
revert Error_EmergencyExitNotActivated();
}
/// -----------------------------------------------------------------------
/// State updates
/// -----------------------------------------------------------------------
PerpetualYieldToken pyt = getPerpetualYieldTokenForVault(vault);
uint256 updatedPricePerVaultShare = getPricePerVaultShare(vault);
// accrue yield
_accrueYield(vault, pyt, msg.sender, updatedPricePerVaultShare);
// burn NYT from the sender
NegativeYieldToken nyt = getNegativeYieldTokenForVault(vault);
nyt.gateBurn(msg.sender, amount);
/// -----------------------------------------------------------------------
/// Effects
/// -----------------------------------------------------------------------
// compute how much of the underlying assets to give the recipient
// rounds down
underlyingAmount = FullMath.mulDiv(
amount,
PRECISION - status.pytPriceInUnderlying,
PRECISION
);
// withdraw underlying from vault to recipient
// don't check balance since user can just withdraw slightly less
// saves gas this way
underlyingAmount = _withdrawFromVault(
recipient,
vault,
underlyingAmount,
updatedPricePerVaultShare,
false
);
}
/// @notice Emergency exit PYTs into the underlying asset. Only callable when emergency exit has
/// been activated for the vault.
/// @param vault The vault to exit PYT for
/// @param xPYT The xPYT contract to use for burning PYT. Set to 0 to burn raw PYT instead.
/// @param amount The amount of PYT to exit
/// @param recipient The recipient of the underlying asset
/// @return underlyingAmount The amount of underlying asset exited
function emergencyExitPerpetualYieldToken(
address vault,
IxPYT xPYT,
uint256 amount,
address recipient
) external virtual returns (uint256 underlyingAmount) {
/// -----------------------------------------------------------------------
/// Validation
/// -----------------------------------------------------------------------
// ensure emergency exit is active
EmergencyExitStatus memory status = emergencyExitStatusOfVault[vault];
if (!status.activated) {
revert Error_EmergencyExitNotActivated();
}
/// -----------------------------------------------------------------------
/// State updates
/// -----------------------------------------------------------------------
PerpetualYieldToken pyt = getPerpetualYieldTokenForVault(vault);
uint256 updatedPricePerVaultShare = getPricePerVaultShare(vault);
// accrue yield
_accrueYield(vault, pyt, msg.sender, updatedPricePerVaultShare);
if (address(xPYT) == address(0)) {
// burn raw PYT from sender
pyt.gateBurn(msg.sender, amount);
} else {
/// -----------------------------------------------------------------------
/// Effects
/// -----------------------------------------------------------------------
// convert xPYT to PYT then burn
xPYT.withdraw(amount, address(this), msg.sender);
pyt.gateBurn(address(this), amount);
}
/// -----------------------------------------------------------------------
/// Effects
/// -----------------------------------------------------------------------
// compute how much of the underlying assets to give the recipient
// rounds down
underlyingAmount = FullMath.mulDiv(
amount,
status.pytPriceInUnderlying,
PRECISION
);
// withdraw underlying from vault to recipient
// don't check balance since user can just withdraw slightly less
// saves gas this way
underlyingAmount = _withdrawFromVault(
recipient,
vault,
underlyingAmount,
updatedPricePerVaultShare,
false
);
}
/// -----------------------------------------------------------------------
/// Internal utilities
/// -----------------------------------------------------------------------
/// @dev Updates the yield earned globally and for a particular user.
function _accrueYield(
address vault,
PerpetualYieldToken pyt,
address user,
uint256 updatedPricePerVaultShare
) internal virtual {
uint256 updatedYieldPerToken = _computeYieldPerToken(
vault,
pyt,
updatedPricePerVaultShare
);
uint256 userYieldPerTokenStored_ = userYieldPerTokenStored[vault][user];
if (userYieldPerTokenStored_ != 0) {
userAccruedYield[vault][user] =
_getClaimableYieldAmount(
vault,
user,
updatedYieldPerToken,
userYieldPerTokenStored_,
pyt.balanceOf(user)
) +
1;
}
yieldPerTokenStored[vault] = updatedYieldPerToken;
pricePerVaultShareStored[vault] = updatedPricePerVaultShare;
userYieldPerTokenStored[vault][user] = updatedYieldPerToken + 1;
}
/// @dev Mints PYTs and NYTs to the recipient given the amount of underlying deposited.
function _enter(
address nytRecipient,
address pytRecipient,
address vault,
IxPYT xPYT,
uint256 underlyingAmount,
uint256 updatedPricePerVaultShare
) internal virtual {
NegativeYieldToken nyt = getNegativeYieldTokenForVault(vault);
if (address(nyt).code.length == 0) {
// token pair hasn't been deployed yet
// do the deployment now
// only need to check nyt since nyt and pyt are always deployed in pairs
factory.deployYieldTokenPair(this, vault);
}
PerpetualYieldToken pyt = getPerpetualYieldTokenForVault(vault);
/// -----------------------------------------------------------------------
/// State updates
/// -----------------------------------------------------------------------
// accrue yield
_accrueYield(
vault,
pyt,
address(xPYT) == address(0) ? pytRecipient : address(xPYT),
updatedPricePerVaultShare
);
// mint NYTs and PYTs
nyt.gateMint(nytRecipient, underlyingAmount);
if (address(xPYT) == address(0)) {
// mint raw PYT to recipient
pyt.gateMint(pytRecipient, underlyingAmount);
} else {
// mint PYT to xPYT contract
pyt.gateMint(address(xPYT), underlyingAmount);
/// -----------------------------------------------------------------------
/// Effects
/// -----------------------------------------------------------------------
// call sweep to mint xPYT using the PYT
xPYT.sweep(pytRecipient);
}
}
/// @dev Burns PYTs and NYTs from msg.sender given the amount of underlying withdrawn.
function _exit(
address vault,
IxPYT xPYT,
uint256 underlyingAmount,
uint256 updatedPricePerVaultShare
) internal virtual {
NegativeYieldToken nyt = getNegativeYieldTokenForVault(vault);
PerpetualYieldToken pyt = getPerpetualYieldTokenForVault(vault);
if (address(nyt).code.length == 0) {
revert Error_TokenPairNotDeployed();
}
/// -----------------------------------------------------------------------
/// State updates
/// -----------------------------------------------------------------------
// accrue yield
_accrueYield(
vault,
pyt,
address(xPYT) == address(0) ? msg.sender : address(this),
updatedPricePerVaultShare
);
// burn NYTs and PYTs
nyt.gateBurn(msg.sender, underlyingAmount);
if (address(xPYT) == address(0)) {
// burn raw PYT from sender
pyt.gateBurn(msg.sender, underlyingAmount);
} else {
/// -----------------------------------------------------------------------
/// Effects
/// -----------------------------------------------------------------------
// convert xPYT to PYT then burn
xPYT.withdraw(underlyingAmount, address(this), msg.sender);
pyt.gateBurn(address(this), underlyingAmount);
}
}
/// @dev Updates storage variables for when a PYT holder claims the accrued yield.
function _claimYield(address vault, uint256 updatedPricePerVaultShare)
internal
virtual
returns (uint256 yieldAmount)
{
/// -----------------------------------------------------------------------
/// Validation
/// -----------------------------------------------------------------------
PerpetualYieldToken pyt = getPerpetualYieldTokenForVault(vault);
if (address(pyt).code.length == 0) {
revert Error_TokenPairNotDeployed();
}
/// -----------------------------------------------------------------------
/// State updates
/// -----------------------------------------------------------------------
// accrue yield
uint256 updatedYieldPerToken = _computeYieldPerToken(
vault,
pyt,
updatedPricePerVaultShare
);
uint256 userYieldPerTokenStored_ = userYieldPerTokenStored[vault][
msg.sender
];
if (userYieldPerTokenStored_ != 0) {
yieldAmount = _getClaimableYieldAmount(
vault,
msg.sender,
updatedYieldPerToken,
userYieldPerTokenStored_,
pyt.balanceOf(msg.sender)
);
}
yieldPerTokenStored[vault] = updatedYieldPerToken;
pricePerVaultShareStored[vault] = updatedPricePerVaultShare;
userYieldPerTokenStored[vault][msg.sender] = updatedYieldPerToken + 1;
if (yieldAmount != 0) {
userAccruedYield[vault][msg.sender] = 1;
}
}
/// @dev Returns the amount of yield claimable by a PerpetualYieldToken holder from a vault.
/// Assumes userYieldPerTokenStored_ != 0. Does not account for protocol fees.
function _getClaimableYieldAmount(
address vault,
address user,
uint256 updatedYieldPerToken,
uint256 userYieldPerTokenStored_,
uint256 userPYTBalance
) internal view virtual returns (uint256 yieldAmount) {
unchecked {
// the stored value is shifted by one
uint256 actualUserYieldPerToken = userYieldPerTokenStored_ - 1;
// updatedYieldPerToken - actualUserYieldPerToken won't underflow since we check updatedYieldPerToken > actualUserYieldPerToken
yieldAmount = FullMath.mulDiv(
userPYTBalance,
updatedYieldPerToken > actualUserYieldPerToken
? updatedYieldPerToken - actualUserYieldPerToken
: 0,
PRECISION
);
uint256 accruedYield = userAccruedYield[vault][user];
if (accruedYield > 1) {
// won't overflow since the sum is at most the totalSupply of the vault's underlying, which
// is at most 256 bits.
// the stored accruedYield value is shifted by one
yieldAmount += accruedYield - 1;
}
}
}
/// @dev Deposits underlying tokens into a vault
/// @param underlying The underlying token to deposit
/// @param underlyingAmount The amount of tokens to deposit
/// @param vault The vault to deposit into
function _depositIntoVault(
ERC20 underlying,
uint256 underlyingAmount,
address vault
) internal virtual;
/// @dev Withdraws underlying tokens from a vault
/// @param recipient The recipient of the underlying tokens
/// @param vault The vault to withdraw from
/// @param underlyingAmount The amount of tokens to withdraw
/// @param pricePerVaultShare The latest price per vault share value
/// @param checkBalance Set to true to withdraw the entire balance if we're trying
/// to withdraw more than the balance (due to rounding errors)
/// @return withdrawnUnderlyingAmount The amount of underlying tokens withdrawn
function _withdrawFromVault(
address recipient,
address vault,
uint256 underlyingAmount,
uint256 pricePerVaultShare,
bool checkBalance
) internal virtual returns (uint256 withdrawnUnderlyingAmount);
/// @dev Converts a vault share amount into an equivalent underlying asset amount
function _vaultSharesAmountToUnderlyingAmount(
address vault,
uint256 vaultSharesAmount,
uint256 pricePerVaultShare
) internal view virtual returns (uint256);
/// @dev Converts a vault share amount into an equivalent underlying asset amount, rounding up
function _vaultSharesAmountToUnderlyingAmountRoundingUp(
address vault,
uint256 vaultSharesAmount,
uint256 pricePerVaultShare
) internal view virtual returns (uint256);
/// @dev Converts an underlying asset amount into an equivalent vault shares amount
function _underlyingAmountToVaultSharesAmount(
address vault,
uint256 underlyingAmount,
uint256 pricePerVaultShare
) internal view virtual returns (uint256);
/// @dev Computes the latest yieldPerToken value for a vault.
function _computeYieldPerToken(
address vault,
PerpetualYieldToken pyt,
uint256 updatedPricePerVaultShare
) internal view virtual returns (uint256) {
uint256 pytTotalSupply = pyt.totalSupply();
if (pytTotalSupply == 0) {
return yieldPerTokenStored[vault];
}
uint256 pricePerVaultShareStored_ = pricePerVaultShareStored[vault];
if (updatedPricePerVaultShare <= pricePerVaultShareStored_) {
// rounding error in vault share or no yield accrued
return yieldPerTokenStored[vault];
}
uint256 newYieldPerTokenAccrued;
unchecked {
// can't underflow since we know updatedPricePerVaultShare > pricePerVaultShareStored_
newYieldPerTokenAccrued = FullMath.mulDiv(
updatedPricePerVaultShare - pricePerVaultShareStored_,
getVaultShareBalance(vault),
pytTotalSupply
);
}
return yieldPerTokenStored[vault] + newYieldPerTokenAccrued;
}
}
/// @title NegativeYieldToken
/// @author zefram.eth
/// @notice The ERC20 contract representing negative yield tokens
contract NegativeYieldToken is BaseERC20 {
/// -----------------------------------------------------------------------
/// Constructor
/// -----------------------------------------------------------------------
constructor(Gate gate_, address vault_)
BaseERC20(
gate_.negativeYieldTokenName(vault_),
gate_.negativeYieldTokenSymbol(vault_),
gate_,
vault_
)
{}
}