Overview
ETH Balance
0 ETH
Eth Value
$0.00More Info
Private Name Tags
ContractCreator
TokenTracker
Latest 25 from a total of 32 transactions
Transaction Hash |
Method
|
Block
|
From
|
To
|
|||||
---|---|---|---|---|---|---|---|---|---|
Approve | 20378454 | 103 days ago | IN | 0 ETH | 0.00018291 | ||||
Approve | 20378448 | 103 days ago | IN | 0 ETH | 0.00020465 | ||||
Approve | 20214003 | 126 days ago | IN | 0 ETH | 0.00023319 | ||||
Approve | 18719463 | 335 days ago | IN | 0 ETH | 0.00101435 | ||||
Approve | 16737861 | 614 days ago | IN | 0 ETH | 0.00127613 | ||||
Approve | 16732136 | 614 days ago | IN | 0 ETH | 0.00086692 | ||||
Transfer | 16577433 | 636 days ago | IN | 0 ETH | 0.00172727 | ||||
Approve | 15661975 | 764 days ago | IN | 0 ETH | 0.00028415 | ||||
Approve | 15525050 | 783 days ago | IN | 0 ETH | 0.00027572 | ||||
Approve | 15376577 | 807 days ago | IN | 0 ETH | 0.00028549 | ||||
Approve | 15361489 | 810 days ago | IN | 0 ETH | 0.0005534 | ||||
Approve | 15356682 | 810 days ago | IN | 0 ETH | 0.00023999 | ||||
Approve | 15333653 | 814 days ago | IN | 0 ETH | 0.00046714 | ||||
Approve | 15333036 | 814 days ago | IN | 0 ETH | 0.00031066 | ||||
Transfer | 15333031 | 814 days ago | IN | 0 ETH | 0.00034834 | ||||
Approve | 15332138 | 814 days ago | IN | 0 ETH | 0.0003331 | ||||
Approve | 15332103 | 814 days ago | IN | 0 ETH | 0.00037422 | ||||
Approve | 15332081 | 814 days ago | IN | 0 ETH | 0.00033651 | ||||
Approve | 15326746 | 815 days ago | IN | 0 ETH | 0.00051801 | ||||
Approve | 15326455 | 815 days ago | IN | 0 ETH | 0.00078325 | ||||
Approve | 15289434 | 821 days ago | IN | 0 ETH | 0.0005351 | ||||
Approve | 15241473 | 828 days ago | IN | 0 ETH | 0.00031814 | ||||
Approve | 15010219 | 866 days ago | IN | 0 ETH | 0.00217023 | ||||
Approve | 15008379 | 866 days ago | IN | 0 ETH | 0.00161333 | ||||
Approve | 15003033 | 867 days ago | IN | 0 ETH | 0.00194143 |
Latest 1 internal transaction
Advanced mode:
Parent Transaction Hash | Block | From | To | |||
---|---|---|---|---|---|---|
14916927 | 882 days ago | Contract Creation | 0 ETH |
Loading...
Loading
Contract Name:
NegativeYieldToken
Compiler Version
v0.8.13+commit.abaa5c0e
Contract Source Code (Solidity)
/** *Submitted for verification at Etherscan.io on 2022-06-06 */ // 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_ ) {} }
Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
[{"inputs":[{"internalType":"contract Gate","name":"gate_","type":"address"},{"internalType":"address","name":"vault_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"Error_NotGate","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"gate","outputs":[{"internalType":"contract Gate","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"gateBurn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"gateMint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"vault","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]
Contract Creation Code
6101206040523480156200001257600080fd5b50604051620015ba380380620015ba8339810160408190526200003591620003b0565b604051631f27f92760e01b81526001600160a01b038281166004830152831690631f27f92790602401600060405180830381865afa1580156200007c573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052620000a6919081019062000405565b604051631c4d2fd760e21b81526001600160a01b038381166004830152841690637134bf5c90602401600060405180830381865afa158015620000ed573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405262000117919081019062000405565b60405163249f75af60e21b81526001600160a01b038085166004830152859185918591859185169063927dd6bc90602401602060405180830381865afa15801562000166573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200018c9190620004e1565b6001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015620001ca573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620001f0919062000508565b825162000205906000906020860190620002f1565b5081516200021b906001906020850190620002f1565b5060ff81166080524660a0526200023162000255565b60c0525050506001600160a01b0391821660e0521661010052506200060c92505050565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f600060405162000289919062000569565b6040805191829003822060208301939093528101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b828054620002ff906200052d565b90600052602060002090601f0160209004810192826200032357600085556200036e565b82601f106200033e57805160ff19168380011785556200036e565b828001600101855582156200036e579182015b828111156200036e57825182559160200191906001019062000351565b506200037c92915062000380565b5090565b5b808211156200037c576000815560010162000381565b6001600160a01b0381168114620003ad57600080fd5b50565b60008060408385031215620003c457600080fd5b8251620003d18162000397565b6020840151909250620003e48162000397565b809150509250929050565b634e487b7160e01b600052604160045260246000fd5b600060208083850312156200041957600080fd5b82516001600160401b03808211156200043157600080fd5b818501915085601f8301126200044657600080fd5b8151818111156200045b576200045b620003ef565b604051601f8201601f19908116603f01168101908382118183101715620004865762000486620003ef565b8160405282815288868487010111156200049f57600080fd5b600093505b82841015620004c35784840186015181850187015292850192620004a4565b82841115620004d55760008684830101525b98975050505050505050565b600060208284031215620004f457600080fd5b8151620005018162000397565b9392505050565b6000602082840312156200051b57600080fd5b815160ff811681146200050157600080fd5b600181811c908216806200054257607f821691505b6020821081036200056357634e487b7160e01b600052602260045260246000fd5b50919050565b600080835481600182811c9150808316806200058657607f831692505b60208084108203620005a657634e487b7160e01b86526022600452602486fd5b818015620005bd5760018114620005cf57620005fe565b60ff19861689528489019650620005fe565b60008a81526020902060005b86811015620005f65781548b820152908501908301620005db565b505084890196505b509498975050505050505050565b60805160a05160c05160e05161010051610f5b6200065f60003960006102c30152600081816101eb015281816105a301526106b20152600061056901526000610534015260006101750152610f5b6000f3fe608060405234801561001057600080fd5b50600436106101005760003560e01c80637a0ebc8811610097578063b132d08f11610066578063b132d08f1461026d578063d505accf14610280578063dd62ed3e14610293578063fbfa77cf146102be57600080fd5b80637a0ebc88146101e65780637ecebe001461023257806395d89b4114610252578063a9059cbb1461025a57600080fd5b8063313ce567116100d3578063313ce567146101705780633644e515146101a957806340471768146101b157806370a08231146101c657600080fd5b806306fdde0314610105578063095ea7b31461012357806318160ddd1461014657806323b872dd1461015d575b600080fd5b61010d6102e5565b60405161011a9190610bd8565b60405180910390f35b610136610131366004610c74565b610373565b604051901515815260200161011a565b61014f60025481565b60405190815260200161011a565b61013661016b366004610c9e565b6103ec565b6101977f000000000000000000000000000000000000000000000000000000000000000081565b60405160ff909116815260200161011a565b61014f610530565b6101c46101bf366004610c74565b61058b565b005b61014f6101d4366004610cda565b60036020526000908152604090205481565b61020d7f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200161011a565b61014f610240366004610cda565b60056020526000908152604090205481565b61010d610608565b610136610268366004610c74565b610615565b6101c461027b366004610c74565b61069a565b6101c461028e366004610cfc565b610713565b61014f6102a1366004610d6f565b600460209081526000928352604080842090915290825290205481565b61020d7f000000000000000000000000000000000000000000000000000000000000000081565b600080546102f290610da2565b80601f016020809104026020016040519081016040528092919081815260200182805461031e90610da2565b801561036b5780601f106103405761010080835404028352916020019161036b565b820191906000526020600020905b81548152906001019060200180831161034e57829003601f168201915b505050505081565b33600081815260046020908152604080832073ffffffffffffffffffffffffffffffffffffffff8716808552925280832085905551919290917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925906103db9086815260200190565b60405180910390a350600192915050565b73ffffffffffffffffffffffffffffffffffffffff831660009081526004602090815260408083203384529091528120547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146104805761044e8382610e24565b73ffffffffffffffffffffffffffffffffffffffff861660009081526004602090815260408083203384529091529020555b73ffffffffffffffffffffffffffffffffffffffff8516600090815260036020526040812080548592906104b5908490610e24565b909155505073ffffffffffffffffffffffffffffffffffffffff808516600081815260036020526040908190208054870190555190918716907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9061051d9087815260200190565b60405180910390a3506001949350505050565b60007f0000000000000000000000000000000000000000000000000000000000000000461461056657610561610a37565b905090565b507f000000000000000000000000000000000000000000000000000000000000000090565b3373ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016146105fa576040517fb8965d0c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6106048282610ad1565b5050565b600180546102f290610da2565b33600090815260036020526040812080548391908390610636908490610e24565b909155505073ffffffffffffffffffffffffffffffffffffffff8316600081815260036020526040908190208054850190555133907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906103db9086815260200190565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614610709576040517fb8965d0c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6106048282610b4a565b42841015610782576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f5045524d49545f444541444c494e455f4558504952454400000000000000000060448201526064015b60405180910390fd5b6000600161078e610530565b73ffffffffffffffffffffffffffffffffffffffff8a811660008181526005602090815260409182902080546001810190915582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98184015280840194909452938d166060840152608083018c905260a083019390935260c08083018b90528151808403909101815260e0830190915280519201919091207f190100000000000000000000000000000000000000000000000000000000000061010083015261010282019290925261012281019190915261014201604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181528282528051602091820120600084529083018083525260ff871690820152606081018590526080810184905260a0016020604051602081039080840390855afa1580156108e0573d6000803e3d6000fd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015191505073ffffffffffffffffffffffffffffffffffffffff81161580159061095b57508773ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16145b6109c1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600e60248201527f494e56414c49445f5349474e45520000000000000000000000000000000000006044820152606401610779565b73ffffffffffffffffffffffffffffffffffffffff90811660009081526004602090815260408083208a8516808552908352928190208990555188815291928a16917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a350505050505050565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f6000604051610a699190610e3b565b6040805191829003822060208301939093528101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b8060026000828254610ae39190610f0d565b909155505073ffffffffffffffffffffffffffffffffffffffff82166000818152600360209081526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef91015b60405180910390a35050565b73ffffffffffffffffffffffffffffffffffffffff821660009081526003602052604081208054839290610b7f908490610e24565b909155505060028054829003905560405181815260009073ffffffffffffffffffffffffffffffffffffffff8416907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef90602001610b3e565b600060208083528351808285015260005b81811015610c0557858101830151858201604001528201610be9565b81811115610c17576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b803573ffffffffffffffffffffffffffffffffffffffff81168114610c6f57600080fd5b919050565b60008060408385031215610c8757600080fd5b610c9083610c4b565b946020939093013593505050565b600080600060608486031215610cb357600080fd5b610cbc84610c4b565b9250610cca60208501610c4b565b9150604084013590509250925092565b600060208284031215610cec57600080fd5b610cf582610c4b565b9392505050565b600080600080600080600060e0888a031215610d1757600080fd5b610d2088610c4b565b9650610d2e60208901610c4b565b95506040880135945060608801359350608088013560ff81168114610d5257600080fd5b9699959850939692959460a0840135945060c09093013592915050565b60008060408385031215610d8257600080fd5b610d8b83610c4b565b9150610d9960208401610c4b565b90509250929050565b600181811c90821680610db657607f821691505b602082108103610def577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600082821015610e3657610e36610df5565b500390565b600080835481600182811c915080831680610e5757607f831692505b60208084108203610e8f577f4e487b710000000000000000000000000000000000000000000000000000000086526022600452602486fd5b818015610ea35760018114610ed257610eff565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00861689528489019650610eff565b60008a81526020902060005b86811015610ef75781548b820152908501908301610ede565b505084890196505b509498975050505050505050565b60008219821115610f2057610f20610df5565b50019056fea2646970667358221220a11195b24f792f2cb954969f59cfbfbceed801b77214aa564d22d213e974851964736f6c634300080d003300000000000000000000000036b49ebf089be8860d7fc60f2553461e9cc8e9e2000000000000000000000000a258c4606ca8206d8aa700ce2143d7db854d168c
Deployed Bytecode
0x608060405234801561001057600080fd5b50600436106101005760003560e01c80637a0ebc8811610097578063b132d08f11610066578063b132d08f1461026d578063d505accf14610280578063dd62ed3e14610293578063fbfa77cf146102be57600080fd5b80637a0ebc88146101e65780637ecebe001461023257806395d89b4114610252578063a9059cbb1461025a57600080fd5b8063313ce567116100d3578063313ce567146101705780633644e515146101a957806340471768146101b157806370a08231146101c657600080fd5b806306fdde0314610105578063095ea7b31461012357806318160ddd1461014657806323b872dd1461015d575b600080fd5b61010d6102e5565b60405161011a9190610bd8565b60405180910390f35b610136610131366004610c74565b610373565b604051901515815260200161011a565b61014f60025481565b60405190815260200161011a565b61013661016b366004610c9e565b6103ec565b6101977f000000000000000000000000000000000000000000000000000000000000001281565b60405160ff909116815260200161011a565b61014f610530565b6101c46101bf366004610c74565b61058b565b005b61014f6101d4366004610cda565b60036020526000908152604090205481565b61020d7f00000000000000000000000036b49ebf089be8860d7fc60f2553461e9cc8e9e281565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200161011a565b61014f610240366004610cda565b60056020526000908152604090205481565b61010d610608565b610136610268366004610c74565b610615565b6101c461027b366004610c74565b61069a565b6101c461028e366004610cfc565b610713565b61014f6102a1366004610d6f565b600460209081526000928352604080842090915290825290205481565b61020d7f000000000000000000000000a258c4606ca8206d8aa700ce2143d7db854d168c81565b600080546102f290610da2565b80601f016020809104026020016040519081016040528092919081815260200182805461031e90610da2565b801561036b5780601f106103405761010080835404028352916020019161036b565b820191906000526020600020905b81548152906001019060200180831161034e57829003601f168201915b505050505081565b33600081815260046020908152604080832073ffffffffffffffffffffffffffffffffffffffff8716808552925280832085905551919290917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925906103db9086815260200190565b60405180910390a350600192915050565b73ffffffffffffffffffffffffffffffffffffffff831660009081526004602090815260408083203384529091528120547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146104805761044e8382610e24565b73ffffffffffffffffffffffffffffffffffffffff861660009081526004602090815260408083203384529091529020555b73ffffffffffffffffffffffffffffffffffffffff8516600090815260036020526040812080548592906104b5908490610e24565b909155505073ffffffffffffffffffffffffffffffffffffffff808516600081815260036020526040908190208054870190555190918716907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9061051d9087815260200190565b60405180910390a3506001949350505050565b60007f0000000000000000000000000000000000000000000000000000000000000001461461056657610561610a37565b905090565b507f312833ae5b98be50c48997836149ae40147190068799d0e4f240773f72cfecdc90565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000036b49ebf089be8860d7fc60f2553461e9cc8e9e216146105fa576040517fb8965d0c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6106048282610ad1565b5050565b600180546102f290610da2565b33600090815260036020526040812080548391908390610636908490610e24565b909155505073ffffffffffffffffffffffffffffffffffffffff8316600081815260036020526040908190208054850190555133907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906103db9086815260200190565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000036b49ebf089be8860d7fc60f2553461e9cc8e9e21614610709576040517fb8965d0c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6106048282610b4a565b42841015610782576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f5045524d49545f444541444c494e455f4558504952454400000000000000000060448201526064015b60405180910390fd5b6000600161078e610530565b73ffffffffffffffffffffffffffffffffffffffff8a811660008181526005602090815260409182902080546001810190915582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98184015280840194909452938d166060840152608083018c905260a083019390935260c08083018b90528151808403909101815260e0830190915280519201919091207f190100000000000000000000000000000000000000000000000000000000000061010083015261010282019290925261012281019190915261014201604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181528282528051602091820120600084529083018083525260ff871690820152606081018590526080810184905260a0016020604051602081039080840390855afa1580156108e0573d6000803e3d6000fd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015191505073ffffffffffffffffffffffffffffffffffffffff81161580159061095b57508773ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16145b6109c1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600e60248201527f494e56414c49445f5349474e45520000000000000000000000000000000000006044820152606401610779565b73ffffffffffffffffffffffffffffffffffffffff90811660009081526004602090815260408083208a8516808552908352928190208990555188815291928a16917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a350505050505050565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f6000604051610a699190610e3b565b6040805191829003822060208301939093528101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b8060026000828254610ae39190610f0d565b909155505073ffffffffffffffffffffffffffffffffffffffff82166000818152600360209081526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef91015b60405180910390a35050565b73ffffffffffffffffffffffffffffffffffffffff821660009081526003602052604081208054839290610b7f908490610e24565b909155505060028054829003905560405181815260009073ffffffffffffffffffffffffffffffffffffffff8416907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef90602001610b3e565b600060208083528351808285015260005b81811015610c0557858101830151858201604001528201610be9565b81811115610c17576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b803573ffffffffffffffffffffffffffffffffffffffff81168114610c6f57600080fd5b919050565b60008060408385031215610c8757600080fd5b610c9083610c4b565b946020939093013593505050565b600080600060608486031215610cb357600080fd5b610cbc84610c4b565b9250610cca60208501610c4b565b9150604084013590509250925092565b600060208284031215610cec57600080fd5b610cf582610c4b565b9392505050565b600080600080600080600060e0888a031215610d1757600080fd5b610d2088610c4b565b9650610d2e60208901610c4b565b95506040880135945060608801359350608088013560ff81168114610d5257600080fd5b9699959850939692959460a0840135945060c09093013592915050565b60008060408385031215610d8257600080fd5b610d8b83610c4b565b9150610d9960208401610c4b565b90509250929050565b600181811c90821680610db657607f821691505b602082108103610def577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600082821015610e3657610e36610df5565b500390565b600080835481600182811c915080831680610e5757607f831692505b60208084108203610e8f577f4e487b710000000000000000000000000000000000000000000000000000000086526022600452602486fd5b818015610ea35760018114610ed257610eff565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00861689528489019650610eff565b60008a81526020902060005b86811015610ef75781548b820152908501908301610ede565b505084890196505b509498975050505050505050565b60008219821115610f2057610f20610df5565b50019056fea2646970667358221220a11195b24f792f2cb954969f59cfbfbceed801b77214aa564d22d213e974851964736f6c634300080d0033
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
00000000000000000000000036b49ebf089be8860d7fc60f2553461e9cc8e9e2000000000000000000000000a258c4606ca8206d8aa700ce2143d7db854d168c
-----Decoded View---------------
Arg [0] : gate_ (address): 0x36b49ebF089BE8860d7fC60f2553461E9Cc8e9e2
Arg [1] : vault_ (address): 0xa258C4606Ca8206D8aA700cE2143D7db854D168c
-----Encoded View---------------
2 Constructor Arguments found :
Arg [0] : 00000000000000000000000036b49ebf089be8860d7fc60f2553461e9cc8e9e2
Arg [1] : 000000000000000000000000a258c4606ca8206d8aa700ce2143d7db854d168c
Deployed Bytecode Sourcemap
105699:458:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3205:18;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;4682:217;;;;;;:::i;:::-;;:::i;:::-;;;1300:14:1;;1293:22;1275:41;;1263:2;1248:18;4682:217:0;1135:187:1;3488:26:0;;;;;;;;;1473:25:1;;;1461:2;1446:18;3488:26:0;1327:177:1;5300:612:0;;;;;;:::i;:::-;;:::i;3261:31::-;;;;;;;;2014:4:1;2002:17;;;1984:36;;1972:2;1957:18;3261:31:0;1842:184:1;7642:179:0;;;:::i;31891:193::-;;;;;;:::i;:::-;;:::i;:::-;;3523:44;;;;;;:::i;:::-;;;;;;;;;;;;;;31177:26;;;;;;;;2593:42:1;2581:55;;;2563:74;;2551:2;2536:18;31177:26:0;2404:239:1;3949:41:0;;;;;;:::i;:::-;;;;;;;;;;;;;;3232:20;;;:::i;4907:385::-;;;;;;:::i;:::-;;:::i;32092:197::-;;;;;;:::i;:::-;;:::i;6107:1527::-;;;;;;:::i;:::-;;:::i;3576:64::-;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;31210:30;;;;;3205:18;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;4682:217::-;4783:10;4756:4;4773:21;;;:9;:21;;;;;;;;;:30;;;;;;;;;;:39;;;4830:37;4756:4;;4773:30;;4830:37;;;;4806:6;1473:25:1;;1461:2;1446:18;;1327:177;4830:37:0;;;;;;;;-1:-1:-1;4887:4:0;4682:217;;;;:::o;5300:612::-;5457:15;;;5422:4;5457:15;;;:9;:15;;;;;;;;5473:10;5457:27;;;;;;;;5548:17;5537:28;;5533:80;;5597:16;5607:6;5597:7;:16;:::i;:::-;5567:15;;;;;;;:9;:15;;;;;;;;5583:10;5567:27;;;;;;;:46;5533:80;5626:15;;;;;;;:9;:15;;;;;:25;;5645:6;;5626:15;:25;;5645:6;;5626:25;:::i;:::-;;;;-1:-1:-1;;5802:13:0;;;;;;;;:9;:13;;;;;;;:23;;;;;;5854:26;5802:13;;5854:26;;;;;;;5819:6;1473:25:1;;1461:2;1446:18;;1327:177;5854:26:0;;;;;;;;-1:-1:-1;5900:4:0;;5300:612;-1:-1:-1;;;;5300:612:0:o;7642:179::-;7699:7;7743:16;7726:13;:33;:87;;7789:24;:22;:24::i;:::-;7719:94;;7642:179;:::o;7726:87::-;-1:-1:-1;7762:24:0;;7642:179::o;31891:193::-;31969:10;:27;31991:4;31969:27;;31965:82;;32020:15;;;;;;;;;;;;;;31965:82;32059:17;32065:2;32069:6;32059:5;:17::i;:::-;31891:193;;:::o;3232:20::-;;;;;;;:::i;4907:385::-;5004:10;4977:4;4994:21;;;:9;:21;;;;;:31;;5019:6;;4994:21;4977:4;;4994:31;;5019:6;;4994:31;:::i;:::-;;;;-1:-1:-1;;5176:13:0;;;;;;;:9;:13;;;;;;;:23;;;;;;5228:32;5237:10;;5228:32;;;;5193:6;1473:25:1;;1461:2;1446:18;;1327:177;32092:197:0;32172:10;:27;32194:4;32172:27;;32168:82;;32223:15;;;;;;;;;;;;;;32168:82;32262:19;32268:4;32274:6;32262:5;:19::i;6107:1527::-;6335:15;6323:8;:27;;6315:63;;;;;;;4805:2:1;6315:63:0;;;4787:21:1;4844:2;4824:18;;;4817:30;4883:25;4863:18;;;4856:53;4926:18;;6315:63:0;;;;;;;;;6548:24;6575:827;6715:18;:16;:18::i;:::-;7169:13;;;;;;;;:6;:13;;;;;;;;;:15;;;;;;;;6800:458;;6845:167;6800:458;;;5242:25:1;5344:18;;;5337:43;;;;5416:15;;;5396:18;;;5389:43;5448:18;;;5441:34;;;5491:19;;;5484:35;;;;5535:19;;;;5528:35;;;6800:458:0;;;;;;;;;;5214:19:1;;;6800:458:0;;;6760:525;;;;;;;;5844:66:1;6635:673:0;;;5832:79:1;5927:11;;;5920:27;;;;5963:12;;;5956:28;;;;6000:12;;6635:673:0;;;;;;;;;;;;;6603:724;;6635:673;6603:724;;;;6575:827;;;;;;;;;6250:25:1;6323:4;6311:17;;6291:18;;;6284:45;6345:18;;;6338:34;;;6388:18;;;6381:34;;;6222:19;;6575:827:0;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;6575:827:0;;;;;;-1:-1:-1;;7427:30:0;;;;;;;:59;;;7481:5;7461:25;;:16;:25;;;7427:59;7419:86;;;;;;;6628:2:1;7419:86:0;;;6610:21:1;6667:2;6647:18;;;6640:30;6706:16;6686:18;;;6679:44;6740:18;;7419:86:0;6426:338:1;7419:86:0;7522:27;;;;;;;;:9;:27;;;;;;;;:36;;;;;;;;;;;;;:44;;;7595:31;1473:25:1;;;7522:36:0;;7595:31;;;;;1446:18:1;7595:31:0;;;;;;;6107:1527;;;;;;;:::o;7829:457::-;7894:7;7995:95;8129:4;8113:22;;;;;;:::i;:::-;;;;;;;;;;7962:301;;;8381:25:1;;;;8422:18;;8415:34;;;;8158:14:0;8465:18:1;;;8458:34;8195:13:0;8508:18:1;;;8501:34;8239:4:0;8551:19:1;;;8544:84;8353:19;;7962:301:0;;;;;;;;;;;;7934:344;;;;;;7914:364;;7829:457;:::o;8486:335::-;8572:6;8557:11;;:21;;;;;;;:::i;:::-;;;;-1:-1:-1;;8729:13:0;;;;;;;:9;:13;;;;;;;;:23;;;;;;8781:32;1473:25:1;;;8781:32:0;;1446:18:1;8781:32:0;;;;;;;;8486:335;;:::o;8829:338::-;8902:15;;;;;;;:9;:15;;;;;:25;;8921:6;;8902:15;:25;;8921:6;;8902:25;:::i;:::-;;;;-1:-1:-1;;9075:11:0;:21;;;;;;;9125:34;;1473:25:1;;;-1:-1:-1;;9125:34:0;;;;;;1461:2:1;1446:18;9125:34:0;1327:177:1;14:656;126:4;155:2;184;173:9;166:21;216:6;210:13;259:6;254:2;243:9;239:18;232:34;284:1;294:140;308:6;305:1;302:13;294:140;;;403:14;;;399:23;;393:30;369:17;;;388:2;365:26;358:66;323:10;;294:140;;;452:6;449:1;446:13;443:91;;;522:1;517:2;508:6;497:9;493:22;489:31;482:42;443:91;-1:-1:-1;586:2:1;574:15;591:66;570:88;555:104;;;;661:2;551:113;;14:656;-1:-1:-1;;;14:656:1:o;675:196::-;743:20;;803:42;792:54;;782:65;;772:93;;861:1;858;851:12;772:93;675:196;;;:::o;876:254::-;944:6;952;1005:2;993:9;984:7;980:23;976:32;973:52;;;1021:1;1018;1011:12;973:52;1044:29;1063:9;1044:29;:::i;:::-;1034:39;1120:2;1105:18;;;;1092:32;;-1:-1:-1;;;876:254:1:o;1509:328::-;1586:6;1594;1602;1655:2;1643:9;1634:7;1630:23;1626:32;1623:52;;;1671:1;1668;1661:12;1623:52;1694:29;1713:9;1694:29;:::i;:::-;1684:39;;1742:38;1776:2;1765:9;1761:18;1742:38;:::i;:::-;1732:48;;1827:2;1816:9;1812:18;1799:32;1789:42;;1509:328;;;;;:::o;2213:186::-;2272:6;2325:2;2313:9;2304:7;2300:23;2296:32;2293:52;;;2341:1;2338;2331:12;2293:52;2364:29;2383:9;2364:29;:::i;:::-;2354:39;2213:186;-1:-1:-1;;;2213:186:1:o;2648:693::-;2759:6;2767;2775;2783;2791;2799;2807;2860:3;2848:9;2839:7;2835:23;2831:33;2828:53;;;2877:1;2874;2867:12;2828:53;2900:29;2919:9;2900:29;:::i;:::-;2890:39;;2948:38;2982:2;2971:9;2967:18;2948:38;:::i;:::-;2938:48;;3033:2;3022:9;3018:18;3005:32;2995:42;;3084:2;3073:9;3069:18;3056:32;3046:42;;3138:3;3127:9;3123:19;3110:33;3183:4;3176:5;3172:16;3165:5;3162:27;3152:55;;3203:1;3200;3193:12;3152:55;2648:693;;;;-1:-1:-1;2648:693:1;;;;3226:5;3278:3;3263:19;;3250:33;;-1:-1:-1;3330:3:1;3315:19;;;3302:33;;2648:693;-1:-1:-1;;2648:693:1:o;3346:260::-;3414:6;3422;3475:2;3463:9;3454:7;3450:23;3446:32;3443:52;;;3491:1;3488;3481:12;3443:52;3514:29;3533:9;3514:29;:::i;:::-;3504:39;;3562:38;3596:2;3585:9;3581:18;3562:38;:::i;:::-;3552:48;;3346:260;;;;;:::o;3842:437::-;3921:1;3917:12;;;;3964;;;3985:61;;4039:4;4031:6;4027:17;4017:27;;3985:61;4092:2;4084:6;4081:14;4061:18;4058:38;4055:218;;4129:77;4126:1;4119:88;4230:4;4227:1;4220:15;4258:4;4255:1;4248:15;4055:218;;3842:437;;;:::o;4284:184::-;4336:77;4333:1;4326:88;4433:4;4430:1;4423:15;4457:4;4454:1;4447:15;4473:125;4513:4;4541:1;4538;4535:8;4532:34;;;4546:18;;:::i;:::-;-1:-1:-1;4583:9:1;;4473:125::o;6898:1219::-;7028:3;7057:1;7090:6;7084:13;7120:3;7142:1;7170:9;7166:2;7162:18;7152:28;;7230:2;7219:9;7215:18;7252;7242:61;;7296:4;7288:6;7284:17;7274:27;;7242:61;7322:2;7370;7362:6;7359:14;7339:18;7336:38;7333:222;;7409:77;7404:3;7397:90;7510:4;7507:1;7500:15;7540:4;7535:3;7528:17;7333:222;7571:18;7598:162;;;;7774:1;7769:323;;;;7564:528;;7598:162;7646:66;7635:9;7631:82;7626:3;7619:95;7743:6;7738:3;7734:16;7727:23;;7598:162;;7769:323;6845:1;6838:14;;;6882:4;6869:18;;7867:1;7881:165;7895:6;7892:1;7889:13;7881:165;;;7973:14;;7960:11;;;7953:35;8016:16;;;;7910:10;;7881:165;;;7885:3;;8075:6;8070:3;8066:16;8059:23;;7564:528;-1:-1:-1;8108:3:1;;6898:1219;-1:-1:-1;;;;;;;;6898:1219:1:o;8639:128::-;8679:3;8710:1;8706:6;8703:1;8700:13;8697:39;;;8716:18;;:::i;:::-;-1:-1:-1;8752:9:1;;8639:128::o
Swarm Source
ipfs://a11195b24f792f2cb954969f59cfbfbceed801b77214aa564d22d213e9748519
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 30 Chains
Chain | Token | Portfolio % | Price | Amount | Value |
---|
Loading...
Loading
[ Download: CSV Export ]
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.