Feature Tip: Add private address tag to any address under My Name Tag !
Overview
ETH Balance
0 ETH
Eth Value
$0.00More Info
Private Name Tags
ContractCreator
Transaction Hash |
Method
|
Block
|
From
|
To
|
|||||
---|---|---|---|---|---|---|---|---|---|
Latest 1 internal transaction
Advanced mode:
Parent Transaction Hash | Block |
From
|
To
|
|||
---|---|---|---|---|---|---|
20585764 | 127 days ago | Contract Creation | 0 ETH |
Loading...
Loading
This contract contains unverified libraries: LPMath, MorphoBlueConversions
This contract may be a proxy contract. Click on More Options and select Is this a proxy? to confirm and enable the "Read as Proxy" & "Write as Proxy" tabs.
Contract Name:
MorphoBlueTarget4Deployer
Compiler Version
v0.8.22+commit.4fc1097e
Optimization Enabled:
Yes with 13000 runs
Other Settings:
paris EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: Apache-2.0 pragma solidity 0.8.22; import { MorphoBlueTarget4 } from "../../instances/morpho-blue/MorphoBlueTarget4.sol"; import { IHyperdrive } from "../../interfaces/IHyperdrive.sol"; import { IHyperdriveAdminController } from "../../interfaces/IHyperdriveAdminController.sol"; import { IHyperdriveTargetDeployer } from "../../interfaces/IHyperdriveTargetDeployer.sol"; import { IMorphoBlueHyperdrive } from "../../interfaces/IMorphoBlueHyperdrive.sol"; /// @author DELV /// @title MorphoBlueTarget4Deployer /// @notice The target4 deployer for the MorphoBlueHyperdrive implementation. /// @custom:disclaimer The language used in this code is for coding convenience /// only, and is not intended to, and does not, have any /// particular legal or regulatory significance. contract MorphoBlueTarget4Deployer is IHyperdriveTargetDeployer { /// @notice Deploys a target4 instance with the given parameters. /// @param _config The configuration of the Hyperdrive pool. /// @param _adminController The admin controller that will specify the /// admin parameters for this instance. /// @param _extraData The extra data for the Morpho instance. This contains /// the market parameters that weren't specified in the config. /// @param _salt The create2 salt used in the deployment. /// @return The address of the newly deployed MorphoBlueTarget4 instance. function deployTarget( IHyperdrive.PoolConfig memory _config, IHyperdriveAdminController _adminController, bytes memory _extraData, bytes32 _salt ) external returns (address) { IMorphoBlueHyperdrive.MorphoBlueParams memory params = abi.decode( _extraData, (IMorphoBlueHyperdrive.MorphoBlueParams) ); return address( // NOTE: We hash the sender with the salt to prevent the // front-running of deployments. new MorphoBlueTarget4{ salt: keccak256(abi.encode(msg.sender, _salt)) }(_config, _adminController, params) ); } }
// SPDX-License-Identifier: Apache-2.0 pragma solidity 0.8.22; import { IHyperdrive } from "../interfaces/IHyperdrive.sol"; import { IHyperdriveAdminController } from "../interfaces/IHyperdriveAdminController.sol"; import { HyperdriveAdmin } from "../internal/HyperdriveAdmin.sol"; import { HyperdriveCheckpoint } from "../internal/HyperdriveCheckpoint.sol"; import { HyperdriveLong } from "../internal/HyperdriveLong.sol"; import { HyperdriveLP } from "../internal/HyperdriveLP.sol"; import { HyperdriveMultiToken } from "../internal/HyperdriveMultiToken.sol"; import { HyperdriveShort } from "../internal/HyperdriveShort.sol"; import { HyperdriveStorage } from "../internal/HyperdriveStorage.sol"; /// @author DELV /// @title HyperdriveTarget4 /// @notice Hyperdrive's target 4 logic contract. /// @custom:disclaimer The language used in this code is for coding convenience /// only, and is not intended to, and does not, have any /// particular legal or regulatory significance. abstract contract HyperdriveTarget4 is HyperdriveAdmin, HyperdriveMultiToken, HyperdriveLP, HyperdriveLong, HyperdriveShort, HyperdriveCheckpoint { /// @notice Instantiates target4. /// @param _config The configuration of the Hyperdrive pool. /// @param __adminController The admin controller that will specify the /// admin parameters for this contract. constructor( IHyperdrive.PoolConfig memory _config, IHyperdriveAdminController __adminController ) HyperdriveStorage(_config, __adminController) {} /// LPs /// /// @notice Allows an LP to burn shares and withdraw from the pool. /// @param _lpShares The LP shares to burn. /// @param _minOutputPerShare The minimum amount the LP expects to receive /// for each withdrawal share that is burned. The units of this /// quantity are either base or vault shares, depending on the value /// of `_options.asBase`. /// @param _options The options that configure how the operation is settled. /// @return The amount the LP removing liquidity receives. The /// units of this quantity are either base or vault shares, depending /// on the value of `_options.asBase`. /// @return The base that the LP receives buys out some of their LP shares, /// but it may not be sufficient to fully buy the LP out. In this /// case, the LP receives withdrawal shares equal in value to the /// present value they are owed. As idle capital becomes available, /// the pool will buy back these shares. function removeLiquidity( uint256 _lpShares, uint256 _minOutputPerShare, IHyperdrive.Options calldata _options ) external returns (uint256, uint256) { return _removeLiquidity(_lpShares, _minOutputPerShare, _options); } /// @notice Redeems withdrawal shares by giving the LP a pro-rata amount of /// the withdrawal pool's proceeds. This function redeems the /// maximum amount of the specified withdrawal shares given the /// amount of withdrawal shares ready to withdraw. /// @param _withdrawalShares The withdrawal shares to redeem. /// @param _minOutputPerShare The minimum amount the LP expects to /// receive for each withdrawal share that is burned. The units of /// this quantity are either base or vault shares, depending on the /// value of `_options.asBase`. /// @param _options The options that configure how the operation is settled. /// @return The amount the LP received. The units of this quantity are /// either base or vault shares, depending on the value of /// `_options.asBase`. /// @return The amount of withdrawal shares that were redeemed. function redeemWithdrawalShares( uint256 _withdrawalShares, uint256 _minOutputPerShare, IHyperdrive.Options calldata _options ) external returns (uint256, uint256) { return _redeemWithdrawalShares( _withdrawalShares, _minOutputPerShare, _options ); } /// Checkpoints /// /// @notice Allows anyone to mint a new checkpoint. /// @param _checkpointTime The time of the checkpoint to create. /// @param _maxIterations The number of iterations to use in the Newton's /// method component of `_distributeExcessIdleSafe`. This defaults to /// `LPMath.SHARE_PROCEEDS_MAX_ITERATIONS` if the specified value is /// smaller than the constant. function checkpoint( uint256 _checkpointTime, uint256 _maxIterations ) external { _checkpoint(_checkpointTime, _maxIterations); } }
// SPDX-License-Identifier: Apache-2.0 pragma solidity 0.8.22; import { IMorpho, MarketParams } from "morpho-blue/src/interfaces/IMorpho.sol"; import { MarketParamsLib } from "morpho-blue/src/libraries/MarketParamsLib.sol"; import { SharesMathLib } from "morpho-blue/src/libraries/SharesMathLib.sol"; import { MorphoBalancesLib } from "morpho-blue/src/libraries/periphery/MorphoBalancesLib.sol"; import { ERC20 } from "openzeppelin/token/ERC20/ERC20.sol"; import { SafeERC20 } from "openzeppelin/token/ERC20/utils/SafeERC20.sol"; import { IHyperdrive } from "../../interfaces/IHyperdrive.sol"; import { IMorphoBlueHyperdrive } from "../../interfaces/IMorphoBlueHyperdrive.sol"; import { HyperdriveBase } from "../../internal/HyperdriveBase.sol"; import { MorphoBlueConversions } from "./MorphoBlueConversions.sol"; /// @author DELV /// @title MorphoBlueBase /// @notice The base contract for the MorphoBlue Hyperdrive implementation. /// @dev This Hyperdrive implementation is designed to work with standard /// MorphoBlue vaults. Non-standard implementations may not work correctly /// and should be carefully checked. /// @custom:disclaimer The language used in this code is for coding convenience /// only, and is not intended to, and does not, have any /// particular legal or regulatory significance. abstract contract MorphoBlueBase is HyperdriveBase { using SafeERC20 for ERC20; using MarketParamsLib for MarketParams; using MorphoBalancesLib for IMorpho; using SharesMathLib for uint256; /// @dev The Morpho Blue contract. IMorpho internal immutable _vault; /// @dev The collateral token for this Morpho Blue market. address internal immutable _collateralToken; /// @dev The oracle for this Morpho Blue market. address internal immutable _oracle; /// @dev The IRM for this Morpho Blue market. address internal immutable _irm; /// @dev The LLTV for this Morpho Blue market. uint256 internal immutable _lltv; /// @notice Instantiates the MorphoBlueHyperdrive base contract. /// @param _params The Morpho Blue params. constructor(IMorphoBlueHyperdrive.MorphoBlueParams memory _params) { // Initialize the Morpho vault immutable. _vault = _params.morpho; // Initialize the market parameters immutables. We don't need an // immutable for the loan token because we set the base token to the // loan token. _collateralToken = _params.collateralToken; _oracle = _params.oracle; _irm = _params.irm; _lltv = _params.lltv; // Approve the Morpho vault with 1 wei. This ensures that all of the // subsequent approvals will be writing to a dirty storage slot. ERC20(address(_baseToken)).forceApprove(address(_vault), 1); } /// Yield Source /// /// @dev Accepts a deposit from the user in base. /// @param _baseAmount The base amount to deposit. /// @return sharesMinted The shares that were minted in the deposit. /// @return value The amount of ETH to refund. Since this yield source isn't /// payable, this is always zero. function _depositWithBase( uint256 _baseAmount, bytes calldata // unused _extraData ) internal override returns (uint256 sharesMinted, uint256 value) { // Take custody of the deposit in base. ERC20(address(_baseToken)).safeTransferFrom( msg.sender, address(this), _baseAmount ); // Deposit the base into the yield source. // // NOTE: We increase the required approval amount by 1 wei so that // the vault ends with an approval of 1 wei. This makes future // approvals cheaper by keeping the storage slot warm. ERC20(address(_baseToken)).forceApprove( address(_vault), _baseAmount + 1 ); (, sharesMinted) = _vault.supply( MarketParams({ loanToken: address(_baseToken), collateralToken: _collateralToken, oracle: _oracle, irm: _irm, lltv: _lltv }), _baseAmount, 0, address(this), new bytes(0) ); // NOTE: Since this yield source isn't payable, the value must be zero. value = 0; return (sharesMinted, value); } /// @dev Deposits with shares are not supported for this integration. function _depositWithShares( uint256, // unused _shareAmount bytes calldata // unused _extraData ) internal pure override { revert IHyperdrive.UnsupportedToken(); } /// @dev Process a withdrawal in base and send the proceeds to the /// destination. /// @param _shareAmount The amount of vault shares to withdraw. /// @param _destination The destination of the withdrawal. /// @return amountWithdrawn The amount of base withdrawn. function _withdrawWithBase( uint256 _shareAmount, address _destination, bytes calldata // unused ) internal override returns (uint256 amountWithdrawn) { uint256 baseAmount = _convertToBase(_shareAmount); if (baseAmount > 0) { (amountWithdrawn, ) = _vault.withdraw( MarketParams({ loanToken: address(_baseToken), collateralToken: _collateralToken, oracle: _oracle, irm: _irm, lltv: _lltv }), _convertToBase(_shareAmount), 0, address(this), _destination ); } return amountWithdrawn; } /// @dev Withdrawals with shares are not supported for this integration. function _withdrawWithShares( uint256, // unused _shareAmount address, // unused _destination bytes calldata // unused ) internal pure override { revert IHyperdrive.UnsupportedToken(); } /// @dev Convert an amount of vault shares to an amount of base. /// @param _shareAmount The vault shares amount. /// @return The base amount. function _convertToBase( uint256 _shareAmount ) internal view override returns (uint256) { return MorphoBlueConversions.convertToBase( _vault, _baseToken, _collateralToken, _oracle, _irm, _lltv, _shareAmount ); } /// @dev Convert an amount of base to an amount of vault shares. /// @param _baseAmount The base amount. /// @return The vault shares amount. function _convertToShares( uint256 _baseAmount ) internal view override returns (uint256) { return MorphoBlueConversions.convertToShares( _vault, _baseToken, _collateralToken, _oracle, _irm, _lltv, _baseAmount ); } /// @dev Gets the total amount of shares held by the pool in the yield /// source. /// @return shareAmount The total amount of shares. function _totalShares() internal view override returns (uint256 shareAmount) { return _vault .position( MarketParams({ loanToken: address(_baseToken), collateralToken: _collateralToken, oracle: _oracle, irm: _irm, lltv: _lltv }).id(), address(this) ) .supplyShares; } /// @dev We override the message value check since this integration is /// not payable. function _checkMessageValue() internal view override { if (msg.value != 0) { revert IHyperdrive.NotPayable(); } } }
// SPDX-License-Identifier: Apache-2.0 pragma solidity 0.8.22; import { IMorpho, MarketParams } from "morpho-blue/src/interfaces/IMorpho.sol"; import { SharesMathLib } from "morpho-blue/src/libraries/SharesMathLib.sol"; import { MorphoBalancesLib } from "morpho-blue/src/libraries/periphery/MorphoBalancesLib.sol"; import { IERC20 } from "../../interfaces/IERC20.sol"; import { FixedPointMath } from "../../libraries/FixedPointMath.sol"; /// @author DELV /// @title MorphoBlueConversions /// @notice The conversion logic for the Morpho Blue integration. /// @custom:disclaimer The language used in this code is for coding convenience /// only, and is not intended to, and does not, have any /// particular legal or regulatory significance. library MorphoBlueConversions { using FixedPointMath for uint256; using MorphoBalancesLib for IMorpho; using SharesMathLib for uint256; /// @dev Convert an amount of vault shares to an amount of base. /// @param _vault The Morpho Blue contract. /// @param _baseToken The base token underlying the Morpho Blue vault. /// @param _collateralToken The collateral token for this Morpho Blue market. /// @param _oracle The oracle for this Morpho Blue market. /// @param _irm The IRM for this Morpho Blue market. /// @param _lltv The LLTV for this Morpho Blue market. /// @param _shareAmount The vault shares amount. /// @return The base amount. function convertToBase( IMorpho _vault, IERC20 _baseToken, address _collateralToken, address _oracle, address _irm, uint256 _lltv, uint256 _shareAmount ) external view returns (uint256) { // Get the total supply assets and shares after interest accrues. ( uint256 totalSupplyAssets, uint256 totalSupplyShares ) = getExpectedSupplyBalances( _vault, _baseToken, _collateralToken, _oracle, _irm, _lltv ); return _shareAmount.toAssetsDown(totalSupplyAssets, totalSupplyShares); } /// @dev Convert an amount of base to an amount of vault shares. /// @param _vault The Morpho Blue vault. /// @param _baseToken The base token underlying the Morpho Blue vault. /// @param _collateralToken The collateral token for this Morpho Blue market. /// @param _oracle The oracle for this Morpho Blue market. /// @param _irm The IRM for this Morpho Blue market. /// @param _lltv The LLTV for this Morpho Blue market. /// @param _baseAmount The base amount. /// @return The vault shares amount. function convertToShares( IMorpho _vault, IERC20 _baseToken, address _collateralToken, address _oracle, address _irm, uint256 _lltv, uint256 _baseAmount ) external view returns (uint256) { // Get the total supply assets and shares after interest accrues. ( uint256 totalSupplyAssets, uint256 totalSupplyShares ) = getExpectedSupplyBalances( _vault, _baseToken, _collateralToken, _oracle, _irm, _lltv ); return _baseAmount.toSharesDown(totalSupplyAssets, totalSupplyShares); } /// @dev Gets the Morpho Blue supply balances after accruing interest. /// @param _vault The Morpho Blue vault. /// @param _baseToken The base token underlying the Morpho Blue vault. /// @param _collateralToken The collateral token for this Morpho Blue market. /// @param _oracle The oracle for this Morpho Blue market. /// @param _irm The IRM for this Morpho Blue market. /// @param _lltv The LLTV for this Morpho Blue market. /// @return totalSupplyAssets The total amount of assets after interest. /// @return totalSupplyShares The total amount of shares after interest. function getExpectedSupplyBalances( IMorpho _vault, IERC20 _baseToken, address _collateralToken, address _oracle, address _irm, uint256 _lltv ) internal view returns (uint256 totalSupplyAssets, uint256 totalSupplyShares) { (totalSupplyAssets, totalSupplyShares, , ) = _vault .expectedMarketBalances( MarketParams({ loanToken: address(_baseToken), collateralToken: _collateralToken, oracle: _oracle, irm: _irm, lltv: _lltv }) ); return (totalSupplyAssets, totalSupplyShares); } }
// SPDX-License-Identifier: Apache-2.0 pragma solidity 0.8.22; import { HyperdriveTarget4 } from "../../external/HyperdriveTarget4.sol"; import { IHyperdrive } from "../../interfaces/IHyperdrive.sol"; import { IHyperdriveAdminController } from "../../interfaces/IHyperdriveAdminController.sol"; import { IMorphoBlueHyperdrive } from "../../interfaces/IMorphoBlueHyperdrive.sol"; import { MorphoBlueBase } from "./MorphoBlueBase.sol"; /// @author DELV /// @title MorphoBlueTarget4 /// @notice MorphoBlueHyperdrive's target4 logic contract. This contract contains /// several stateful functions that couldn't fit into the Hyperdrive /// contract. /// @custom:disclaimer The language used in this code is for coding convenience /// only, and is not intended to, and does not, have any /// particular legal or regulatory significance. contract MorphoBlueTarget4 is HyperdriveTarget4, MorphoBlueBase { /// @notice Initializes the target4 contract. /// @param _config The configuration of the Hyperdrive pool. /// @param __adminController The admin controller that will specify the /// admin parameters for this instance. /// @param _params The Morpho Blue params. constructor( IHyperdrive.PoolConfig memory _config, IHyperdriveAdminController __adminController, IMorphoBlueHyperdrive.MorphoBlueParams memory _params ) HyperdriveTarget4(_config, __adminController) MorphoBlueBase(_params) {} }
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; interface IERC20 { /// @notice Emitted when tokens are transferred from one account to another. event Transfer(address indexed from, address indexed to, uint256 value); /// @notice Emitted when an owner changes the approval for a spender. event Approval( address indexed owner, address indexed spender, uint256 value ); /// @notice Updates the allowance of a spender on behalf of the sender. /// @param spender The account with the allowance. /// @param amount The new allowance of the spender. /// @return A flag indicating whether or not the approval succeeded. function approve(address spender, uint256 amount) external returns (bool); /// @notice Transfers tokens from the sender's account to another account. /// @param to The recipient of the tokens. /// @param amount The amount of tokens that will be transferred. /// @return A flag indicating whether or not the transfer succeeded. function transfer(address to, uint256 amount) external returns (bool); /// @notice Transfers tokens from an owner to a recipient. This draws from /// the sender's allowance. /// @param from The owner of the tokens. /// @param to The recipient of the tokens. /// @param amount The amount of tokens that will be transferred. /// @return A flag indicating whether or not the transfer succeeded. function transferFrom( address from, address to, uint256 amount ) external returns (bool); /// @notice Gets the token's name. /// @return The token's name. function name() external view returns (string memory); /// @notice Gets the token's symbol. /// @return The token's symbol. function symbol() external view returns (string memory); /// @notice Gets the token's decimals. /// @return The token's decimals. function decimals() external view returns (uint8); /// @notice Gets the token's total supply. /// @return The token's total supply. function totalSupply() external view returns (uint256); /// @notice Gets the allowance of a spender for an owner. /// @param owner The owner of the tokens. /// @param spender The spender of the tokens. /// @return The allowance of the spender for the owner. function allowance( address owner, address spender ) external view returns (uint256); /// @notice Gets the balance of an account. /// @param account The owner of the tokens. /// @return The account's balance. function balanceOf(address account) external view returns (uint256); }
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; import { IERC20 } from "./IERC20.sol"; import { IHyperdriveCore } from "./IHyperdriveCore.sol"; import { IHyperdriveEvents } from "./IHyperdriveEvents.sol"; import { IHyperdriveRead } from "./IHyperdriveRead.sol"; import { IMultiToken } from "./IMultiToken.sol"; interface IHyperdrive is IHyperdriveEvents, IHyperdriveRead, IHyperdriveCore, IMultiToken { /// Structs /// struct MarketState { /// @dev The pool's share reserves. uint128 shareReserves; /// @dev The pool's bond reserves. uint128 bondReserves; /// @dev The global exposure of the pool due to open longs uint128 longExposure; /// @dev The amount of longs that are still open. uint128 longsOutstanding; /// @dev The net amount of shares that have been added and removed from /// the share reserves due to flat updates. int128 shareAdjustment; /// @dev The amount of shorts that are still open. uint128 shortsOutstanding; /// @dev The average maturity time of outstanding long positions. uint128 longAverageMaturityTime; /// @dev The average maturity time of outstanding short positions. uint128 shortAverageMaturityTime; /// @dev A flag indicating whether or not the pool has been initialized. bool isInitialized; /// @dev A flag indicating whether or not the pool is paused. bool isPaused; /// @dev The proceeds in base of the unredeemed matured positions. uint112 zombieBaseProceeds; /// @dev The shares reserved for unredeemed matured positions. uint128 zombieShareReserves; } struct Checkpoint { /// @dev The time-weighted average spot price of the checkpoint. This is /// used to implement circuit-breakers that prevents liquidity from /// being added when the pool's rate moves too quickly. uint128 weightedSpotPrice; /// @dev The last time the weighted spot price was updated. uint128 lastWeightedSpotPriceUpdateTime; /// @dev The vault share price during the first transaction in the /// checkpoint. This is used to track the amount of interest /// accrued by shorts as well as the vault share price at closing /// of matured longs and shorts. uint128 vaultSharePrice; } struct WithdrawPool { /// @dev The amount of withdrawal shares that are ready to be redeemed. uint128 readyToWithdraw; /// @dev The proceeds recovered by the withdrawal pool. uint128 proceeds; } struct Fees { /// @dev The LP fee applied to the curve portion of a trade. uint256 curve; /// @dev The LP fee applied to the flat portion of a trade. uint256 flat; /// @dev The portion of the LP fee that goes to governance. uint256 governanceLP; /// @dev The portion of the zombie interest that goes to governance. uint256 governanceZombie; } struct PoolDeployConfig { /// @dev The address of the base token. IERC20 baseToken; /// @dev The address of the vault shares token. IERC20 vaultSharesToken; /// @dev The linker factory used by this Hyperdrive instance. address linkerFactory; /// @dev The hash of the ERC20 linker's code. This is used to derive the /// create2 addresses of the ERC20 linkers used by this instance. bytes32 linkerCodeHash; /// @dev The minimum share reserves. uint256 minimumShareReserves; /// @dev The minimum amount of tokens that a position can be opened or /// closed with. uint256 minimumTransactionAmount; /// @dev The maximum delta between the last checkpoint's weighted spot /// APR and the current spot APR for an LP to add liquidity. This /// protects LPs from sandwich attacks. uint256 circuitBreakerDelta; /// @dev The duration of a position prior to maturity. uint256 positionDuration; /// @dev The duration of a checkpoint. uint256 checkpointDuration; /// @dev A parameter which decreases slippage around a target rate. uint256 timeStretch; /// @dev The address of the governance contract. address governance; /// @dev The address which collects governance fees address feeCollector; /// @dev The address which collects swept tokens. address sweepCollector; /// @dev The address that will reward checkpoint minters. address checkpointRewarder; /// @dev The fees applied to trades. IHyperdrive.Fees fees; } struct PoolConfig { /// @dev The address of the base token. IERC20 baseToken; /// @dev The address of the vault shares token. IERC20 vaultSharesToken; /// @dev The linker factory used by this Hyperdrive instance. address linkerFactory; /// @dev The hash of the ERC20 linker's code. This is used to derive the /// create2 addresses of the ERC20 linkers used by this instance. bytes32 linkerCodeHash; /// @dev The initial vault share price. uint256 initialVaultSharePrice; /// @dev The minimum share reserves. uint256 minimumShareReserves; /// @dev The minimum amount of tokens that a position can be opened or /// closed with. uint256 minimumTransactionAmount; /// @dev The maximum delta between the last checkpoint's weighted spot /// APR and the current spot APR for an LP to add liquidity. This /// protects LPs from sandwich attacks. uint256 circuitBreakerDelta; /// @dev The duration of a position prior to maturity. uint256 positionDuration; /// @dev The duration of a checkpoint. uint256 checkpointDuration; /// @dev A parameter which decreases slippage around a target rate. uint256 timeStretch; /// @dev The address of the governance contract. address governance; /// @dev The address which collects governance fees address feeCollector; /// @dev The address which collects swept tokens. address sweepCollector; /// @dev The address that will reward checkpoint minters. address checkpointRewarder; /// @dev The fees applied to trades. IHyperdrive.Fees fees; } struct PoolInfo { /// @dev The reserves of shares held by the pool. uint256 shareReserves; /// @dev The adjustment applied to the share reserves when pricing /// bonds. This is used to ensure that the pricing mechanism is /// held invariant under flat updates for security reasons. int256 shareAdjustment; /// @dev The proceeds in base of the unredeemed matured positions. uint256 zombieBaseProceeds; /// @dev The shares reserved for unredeemed matured positions. uint256 zombieShareReserves; /// @dev The reserves of bonds held by the pool. uint256 bondReserves; /// @dev The total supply of LP shares. uint256 lpTotalSupply; /// @dev The current vault share price. uint256 vaultSharePrice; /// @dev An amount of bonds representing outstanding unmatured longs. uint256 longsOutstanding; /// @dev The average maturity time of the outstanding longs. uint256 longAverageMaturityTime; /// @dev An amount of bonds representing outstanding unmatured shorts. uint256 shortsOutstanding; /// @dev The average maturity time of the outstanding shorts. uint256 shortAverageMaturityTime; /// @dev The amount of withdrawal shares that are ready to be redeemed. uint256 withdrawalSharesReadyToWithdraw; /// @dev The proceeds recovered by the withdrawal pool. uint256 withdrawalSharesProceeds; /// @dev The share price of LP shares. This can be used to mark LP /// shares to market. uint256 lpSharePrice; /// @dev The global exposure of the pool due to open positions uint256 longExposure; } struct Options { /// @dev The address that receives the proceeds of a trade or LP action. address destination; /// @dev A boolean indicating that the trade or LP action should be /// settled in base if true and in the yield source shares if false. bool asBase; /// @dev Additional data that can be used to implement custom logic in /// implementation contracts. By convention, the last 32 bytes of /// extra data are ignored by instances and "passed through" to the /// event. This can be used to pass metadata through transactions. bytes extraData; } /// Errors /// /// @notice Thrown when the inputs to a batch transfer don't match in /// length. error BatchInputLengthMismatch(); /// @notice Thrown when the initializer doesn't provide sufficient liquidity /// to cover the minimum share reserves and the LP shares that are /// burned on initialization. error BelowMinimumContribution(); /// @notice Thrown when the add liquidity circuit breaker is triggered. error CircuitBreakerTriggered(); /// @notice Thrown when the exponent to `FixedPointMath.exp` would cause the /// the result to be larger than the representable scale. error ExpInvalidExponent(); /// @notice Thrown when a permit signature is expired. error ExpiredDeadline(); /// @notice Thrown when a user doesn't have a sufficient balance to perform /// an action. error InsufficientBalance(); /// @notice Thrown when the pool doesn't have sufficient liquidity to /// complete the trade. error InsufficientLiquidity(); /// @notice Thrown when the pool's APR is outside the bounds specified by /// a LP when they are adding liquidity. error InvalidApr(); /// @notice Thrown when the checkpoint time provided to `checkpoint` is /// larger than the current checkpoint or isn't divisible by the /// checkpoint duration. error InvalidCheckpointTime(); /// @notice Thrown when the effective share reserves don't exceed the /// minimum share reserves when the pool is initialized. error InvalidEffectiveShareReserves(); /// @notice Thrown when the caller of one of MultiToken's bridge-only /// functions is not the corresponding bridge. error InvalidERC20Bridge(); /// @notice Thrown when a destination other than the fee collector is /// specified in `collectGovernanceFee`. error InvalidFeeDestination(); /// @notice Thrown when the initial share price doesn't match the share /// price of the underlying yield source on deployment. error InvalidInitialVaultSharePrice(); /// @notice Thrown when the LP share price couldn't be calculated in a /// critical situation. error InvalidLPSharePrice(); /// @notice Thrown when the present value calculation fails. error InvalidPresentValue(); /// @notice Thrown when an invalid signature is used provide permit access /// to the MultiToken. A signature is considered to be invalid if /// it fails to recover to the owner's address. error InvalidSignature(); /// @notice Thrown when the timestamp used to construct an asset ID exceeds /// the uint248 scale. error InvalidTimestamp(); /// @notice Thrown when the input to `FixedPointMath.ln` is less than or /// equal to zero. error LnInvalidInput(); /// @notice Thrown when vault share price is smaller than the minimum share /// price. This protects traders from unknowingly opening a long or /// short after negative interest has accrued. error MinimumSharePrice(); /// @notice Thrown when the input or output amount of a trade is smaller /// than the minimum transaction amount. This protects traders and /// LPs from losses of precision that can occur at small scales. error MinimumTransactionAmount(); /// @notice Thrown when the present value prior to adding liquidity results in a /// decrease in present value after liquidity. This is caused by a /// shortage in liquidity that prevents all the open positions being /// closed on the curve and therefore marked to 1. error DecreasedPresentValueWhenAddingLiquidity(); /// @notice Thrown when ether is sent to an instance that doesn't accept /// ether as a deposit asset. error NotPayable(); /// @notice Thrown when a slippage guard is violated. error OutputLimit(); /// @notice Thrown when the pool is already initialized and a trader calls /// `initialize`. This prevents the pool from being reinitialized /// after it has been initialized. error PoolAlreadyInitialized(); /// @notice Thrown when the pool is paused and a trader tries to add /// liquidity, open a long, or open a short. Traders can still /// close their existing positions while the pool is paused. error PoolIsPaused(); /// @notice Thrown when the owner passed to permit is the zero address. This /// prevents users from spending the funds in address zero by /// sending an invalid signature to ecrecover. error RestrictedZeroAddress(); /// @notice Thrown by a read-only function called by the proxy. Unlike a /// normal error, this error actually indicates that a read-only /// call succeeded. The data that it wraps is the return data from /// the read-only call. error ReturnData(bytes data); /// @notice Thrown when an asset is swept from the pool and one of the /// pool's depository assets changes. error SweepFailed(); /// @notice Thrown when the distribute excess idle calculation fails due /// to the starting present value calculation failing. error DistributeExcessIdleFailed(); /// @notice Thrown when an ether transfer fails. error TransferFailed(); /// @notice Thrown when an unauthorized user attempts to access admin /// functionality. error Unauthorized(); /// @notice Thrown when a read-only call succeeds. The proxy architecture /// uses a force-revert delegatecall pattern to ensure that calls /// that are intended to be read-only are actually read-only. error UnexpectedSuccess(); /// @notice Thrown when casting a value to a int128 that is outside of the /// int128 scale. error UnsafeCastToInt128(); /// @notice Thrown when casting a value to a int256 that is outside of the /// int256 scale. error UnsafeCastToInt256(); /// @notice Thrown when casting a value to a uint112 that is outside of the /// uint128 scale. error UnsafeCastToUint112(); /// @notice Thrown when casting a value to a uint128 that is outside of the /// uint128 scale. error UnsafeCastToUint128(); /// @notice Thrown when casting a value to a uint256 that is outside of the /// uint256 scale. error UnsafeCastToUint256(); /// @notice Thrown when an unsupported option is passed to a function or /// a user attempts to sweep an invalid token. The options and sweep /// targets that are supported vary between instances. error UnsupportedToken(); /// @notice Thrown when `LPMath.calculateUpdateLiquidity` fails. error UpdateLiquidityFailed(); /// Getters /// /// @notice Gets the target0 address. /// @return The target0 address. function target0() external view returns (address); /// @notice Gets the target1 address. /// @return The target1 address. function target1() external view returns (address); /// @notice Gets the target2 address. /// @return The target2 address. function target2() external view returns (address); /// @notice Gets the target3 address. /// @return The target3 address. function target3() external view returns (address); /// @notice Gets the target4 address. /// @return The target4 address. function target4() external view returns (address); }
// SPDX-License-Identifier: Apache-2.0 pragma solidity 0.8.22; interface IHyperdriveAdminController { /// @notice Returns the Hyperdrive governance address. /// @return The Hyperdrive governance address. function hyperdriveGovernance() external view returns (address); /// @notice Returns the fee collector that is the target of fee collections. /// @return The fee collector. function feeCollector() external view returns (address); /// @notice Returns the sweep collector that can sweep stuck tokens from /// Hyperdrive instances. /// @return The sweep collector. function sweepCollector() external view returns (address); /// @notice Returns the checkpoint rewarder that can pay out rewards to /// checkpoint minters. /// @return The checkpoint rewarder. function checkpointRewarder() external view returns (address); // TODO: A better interface would be `isPauser`, but this can't be changed // without swapping out the factory. // /// @notice Returns the checkpoint rewarder that can pay out rewards to /// checkpoint minters. /// @return The checkpoint rewarder. function defaultPausers() external view returns (address[] memory); }
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; import { IERC20 } from "./IERC20.sol"; import { IHyperdriveCheckpointSubrewarder } from "../interfaces/IHyperdriveCheckpointSubrewarder.sol"; interface IHyperdriveCheckpointRewarder { /// @notice Emitted when the admin is transferred. event AdminUpdated(address indexed admin); /// @notice Emitted when the subrewarder is updated. event SubrewarderUpdated( IHyperdriveCheckpointSubrewarder indexed subrewarder ); /// @notice Emitted when a checkpoint reward is claimed. event CheckpointRewardClaimed( address indexed instance, address indexed claimant, bool indexed isTrader, uint256 checkpointTime, IERC20 rewardToken, uint256 rewardAmount ); /// @notice Thrown when caller is not governance. error Unauthorized(); /// @notice Allows the admin to transfer the admin role. /// @param _admin The new admin address. function updateAdmin(address _admin) external; /// @notice Allows the admin to update the subrewarder. /// @param _subrewarder The rewarder that will be delegated to. function updateSubrewarder( IHyperdriveCheckpointSubrewarder _subrewarder ) external; /// @notice Claims a checkpoint reward. /// @param _claimant The address that is claiming the checkpoint reward. /// @param _checkpointTime The time of the checkpoint that was minted. /// @param _isTrader A boolean indicating whether or not the checkpoint was /// minted by a trader or by someone calling checkpoint directly. function claimCheckpointReward( address _claimant, uint256 _checkpointTime, bool _isTrader ) external; /// @notice Gets the rewarder's name. /// @return The rewarder's name. function name() external view returns (string memory); /// @notice Gets the rewarder's kind. /// @return The rewarder's kind. function kind() external pure returns (string memory); /// @notice Gets the rewarder's version. /// @return The rewarder's version. function version() external pure returns (string memory); /// @notice Returns the admin address that updates the rewarder's /// configuration. /// @return The rewarder's admin address. function admin() external view returns (address); /// @notice Returns the subrewarder address that processes checkpoint /// rewards. /// @return The rewarder's subrewarder address. function subrewarder() external view returns (IHyperdriveCheckpointSubrewarder); }
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; import { IERC20 } from "./IERC20.sol"; import { IHyperdriveRegistry } from "../interfaces/IHyperdriveRegistry.sol"; interface IHyperdriveCheckpointSubrewarder { /// @notice Emitted when the admin is transferred. event AdminUpdated(address indexed admin); /// @notice Emitted when the registry is updated. event RegistryUpdated(IHyperdriveRegistry indexed registry); /// @notice Emitted when the reward token is updated. event RewardTokenUpdated(IERC20 indexed rewardToken); /// @notice Emitted when the source is updated. event SourceUpdated(address indexed source); /// @notice Emitted when the trader reward amount is updated. event TraderRewardAmountUpdated(uint256 indexed traderRewardAmount); /// @notice Emitted when the minter reward amount is updated. event MinterRewardAmountUpdated(uint256 indexed minterRewardAmount); /// @notice Thrown when caller is not governance. error Unauthorized(); /// @notice Allows the admin to transfer the admin role. /// @param _admin The new admin address. function updateAdmin(address _admin) external; /// @notice Allows the admin to update the source address that supplies the /// rewards. /// @param _source The new source address that will supply the rewards. function updateSource(address _source) external; /// @notice Allows the admin to update the reward token. /// @param _rewardToken The new reward token. function updateRewardToken(IERC20 _rewardToken) external; /// @notice Allows the admin to update the registry. /// @param _registry The new registry. function updateRegistry(IHyperdriveRegistry _registry) external; /// @notice Allows the admin to update the minter reward amount. /// @param _minterRewardAmount The new minter reward amount. function updateMinterRewardAmount(uint256 _minterRewardAmount) external; /// @notice Allows the admin to update the trader reward amount. /// @param _traderRewardAmount The new trader reward amount. function updateTraderRewardAmount(uint256 _traderRewardAmount) external; /// @notice Processes a checkpoint reward. /// @param _instance The instance that submitted the claim. /// @param _claimant The address that is claiming the checkpoint reward. /// @param _checkpointTime The time of the checkpoint that was minted. /// @param _isTrader A boolean indicating whether or not the checkpoint was /// minted by a trader or by someone calling checkpoint directly. /// @return The reward token that was transferred. /// @return The reward amount. function processReward( address _instance, address _claimant, uint256 _checkpointTime, bool _isTrader ) external returns (IERC20, uint256); /// @notice Gets the subrewarder's name. /// @return The subrewarder's name. function name() external view returns (string memory); /// @notice Gets the subrewarder's kind. /// @return The subrewarder's kind. function kind() external pure returns (string memory); /// @notice Gets the subrewarder's version. /// @return The subrewarder's version. function version() external pure returns (string memory); /// @notice Gets the rewarder address that can delegate to this subrewarder. /// @return The rewarder address. function rewarder() external view returns (address); /// @notice Gets the admin address. /// @return The admin address. function admin() external view returns (address); /// @notice Gets the address that is the source for the reward funds. /// @return The source address. function source() external view returns (address); /// @notice Gets the associated registry. This is what will be used to /// determine which instances should receive checkpoint rewards. /// @return The registry address. function registry() external view returns (IHyperdriveRegistry); /// @notice Gets the reward token. /// @return The reward token. function rewardToken() external view returns (IERC20); /// @notice Gets the minter reward amount. This is the reward amount paid /// when checkpoints are minted through the `checkpoint` function. /// @return The minter reward amount. function minterRewardAmount() external view returns (uint256); /// @notice Gets the trader reward amount. This is the reward amount paid /// when checkpoints are minted through `openLong`, `openShort`, /// `closeLong`, `closeShort`, `addLiquidity`, `removeLiquidity`, or /// `redeemWithdrawalShares`. function traderRewardAmount() external view returns (uint256); }
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; import { IERC20 } from "./IERC20.sol"; import { IHyperdrive } from "./IHyperdrive.sol"; import { IMultiTokenCore } from "./IMultiTokenCore.sol"; interface IHyperdriveCore is IMultiTokenCore { /// Longs /// /// @notice Opens a long position. /// @param _amount The amount of capital provided to open the long. The /// units of this quantity are either base or vault shares, depending /// on the value of `_options.asBase`. /// @param _minOutput The minimum number of bonds to receive. /// @param _minVaultSharePrice The minimum vault share price at which to /// open the long. This allows traders to protect themselves from /// opening a long in a checkpoint where negative interest has /// accrued. /// @param _options The options that configure how the trade is settled. /// @return maturityTime The maturity time of the bonds. /// @return bondProceeds The amount of bonds the user received. function openLong( uint256 _amount, uint256 _minOutput, uint256 _minVaultSharePrice, IHyperdrive.Options calldata _options ) external payable returns (uint256 maturityTime, uint256 bondProceeds); /// @notice Closes a long position with a specified maturity time. /// @param _maturityTime The maturity time of the long. /// @param _bondAmount The amount of longs to close. /// @param _minOutput The minimum proceeds the trader will accept. The units /// of this quantity are either base or vault shares, depending on /// the value of `_options.asBase`. /// @param _options The options that configure how the trade is settled. /// @return proceeds The proceeds the user receives. The units of this /// quantity are either base or vault shares, depending on the value /// of `_options.asBase`. function closeLong( uint256 _maturityTime, uint256 _bondAmount, uint256 _minOutput, IHyperdrive.Options calldata _options ) external returns (uint256 proceeds); /// Shorts /// /// @notice Opens a short position. /// @param _bondAmount The amount of bonds to short. /// @param _maxDeposit The most the user expects to deposit for this trade. /// The units of this quantity are either base or vault shares, /// depending on the value of `_options.asBase`. /// @param _minVaultSharePrice The minimum vault share price at which to open /// the short. This allows traders to protect themselves from opening /// a short in a checkpoint where negative interest has accrued. /// @param _options The options that configure how the trade is settled. /// @return maturityTime The maturity time of the short. /// @return deposit The amount the user deposited for this trade. The units /// of this quantity are either base or vault shares, depending on /// the value of `_options.asBase`. function openShort( uint256 _bondAmount, uint256 _maxDeposit, uint256 _minVaultSharePrice, IHyperdrive.Options calldata _options ) external payable returns (uint256 maturityTime, uint256 deposit); /// @notice Closes a short position with a specified maturity time. /// @param _maturityTime The maturity time of the short. /// @param _bondAmount The amount of shorts to close. /// @param _minOutput The minimum output of this trade. The units of this /// quantity are either base or vault shares, depending on the value /// of `_options.asBase`. /// @param _options The options that configure how the trade is settled. /// @return proceeds The proceeds of closing this short. The units of this /// quantity are either base or vault shares, depending on the value /// of `_options.asBase`. function closeShort( uint256 _maturityTime, uint256 _bondAmount, uint256 _minOutput, IHyperdrive.Options calldata _options ) external returns (uint256 proceeds); /// LPs /// /// @notice Allows the first LP to initialize the market with a target APR. /// @param _contribution The amount of capital to supply. The units of this /// quantity are either base or vault shares, depending on the value /// of `_options.asBase`. /// @param _apr The target APR. /// @param _options The options that configure how the operation is settled. /// @return lpShares The initial number of LP shares created. function initialize( uint256 _contribution, uint256 _apr, IHyperdrive.Options calldata _options ) external payable returns (uint256 lpShares); /// @notice Allows LPs to supply liquidity for LP shares. /// @param _contribution The amount of capital to supply. The units of this /// quantity are either base or vault shares, depending on the value /// of `_options.asBase`. /// @param _minLpSharePrice The minimum LP share price the LP is willing /// to accept for their shares. LPs incur negative slippage when /// adding liquidity if there is a net curve position in the market, /// so this allows LPs to protect themselves from high levels of /// slippage. The units of this quantity are either base or vault /// shares, depending on the value of `_options.asBase`. /// @param _minApr The minimum APR at which the LP is willing to supply. /// @param _maxApr The maximum APR at which the LP is willing to supply. /// @param _options The options that configure how the operation is settled. /// @return lpShares The LP shares received by the LP. function addLiquidity( uint256 _contribution, uint256 _minLpSharePrice, uint256 _minApr, uint256 _maxApr, IHyperdrive.Options calldata _options ) external payable returns (uint256 lpShares); /// @notice Allows an LP to burn shares and withdraw from the pool. /// @param _lpShares The LP shares to burn. /// @param _minOutputPerShare The minimum amount the LP expects to receive /// for each withdrawal share that is burned. The units of this /// quantity are either base or vault shares, depending on the value /// of `_options.asBase`. /// @param _options The options that configure how the operation is settled. /// @return proceeds The amount the LP removing liquidity receives. The /// units of this quantity are either base or vault shares, /// depending on the value of `_options.asBase`. /// @return withdrawalShares The base that the LP receives buys out some of /// their LP shares, but it may not be sufficient to fully buy the /// LP out. In this case, the LP receives withdrawal shares equal in /// value to the present value they are owed. As idle capital /// becomes available, the pool will buy back these shares. function removeLiquidity( uint256 _lpShares, uint256 _minOutputPerShare, IHyperdrive.Options calldata _options ) external returns (uint256 proceeds, uint256 withdrawalShares); /// @notice Redeems withdrawal shares by giving the LP a pro-rata amount of /// the withdrawal pool's proceeds. This function redeems the /// maximum amount of the specified withdrawal shares given the /// amount of withdrawal shares ready to withdraw. /// @param _withdrawalShares The withdrawal shares to redeem. /// @param _minOutputPerShare The minimum amount the LP expects to /// receive for each withdrawal share that is burned. The units of /// this quantity are either base or vault shares, depending on the /// value of `_options.asBase`. /// @param _options The options that configure how the operation is settled. /// @return proceeds The amount the LP received. The units of this quantity /// are either base or vault shares, depending on the value of /// `_options.asBase`. /// @return withdrawalSharesRedeemed The amount of withdrawal shares that /// were redeemed. function redeemWithdrawalShares( uint256 _withdrawalShares, uint256 _minOutputPerShare, IHyperdrive.Options calldata _options ) external returns (uint256 proceeds, uint256 withdrawalSharesRedeemed); /// Checkpoints /// /// @notice Attempts to mint a checkpoint with the specified checkpoint time. /// @param _checkpointTime The time of the checkpoint to create. /// @param _maxIterations The number of iterations to use in the Newton's /// method component of `_distributeExcessIdleSafe`. This defaults to /// `LPMath.SHARE_PROCEEDS_MAX_ITERATIONS` if the specified value is /// smaller than the constant. function checkpoint( uint256 _checkpointTime, uint256 _maxIterations ) external; /// Admin /// // TODO: This function doesn't do anything anymore and is only here for // backwards compatability. This can be removed when the factory is upgraded. // /// @notice A stub for the old setPauser functions that doesn't do anything /// anymore. /// @dev Don't call this. It doesn't do anything. function setGovernance(address _who) external; // TODO: This function doesn't do anything anymore and is only here for // backwards compatability. This can be removed when the factory is upgraded. // /// @notice A stub for the old setPauser functions that doesn't do anything /// anymore. /// @dev Don't call this. It doesn't do anything. function setPauser(address, bool) external; /// @notice This function collects the governance fees accrued by the pool. /// @param _options The options that configure how the fees are settled. /// @return proceeds The governance fees collected. The units of this /// quantity are either base or vault shares, depending on the value /// of `_options.asBase`. function collectGovernanceFee( IHyperdrive.Options calldata _options ) external returns (uint256 proceeds); /// @notice Allows an authorized address to pause this contract. /// @param _status True to pause all deposits and false to unpause them. function pause(bool _status) external; /// @notice Transfers the contract's balance of a target token to the fee /// collector address. /// @dev WARN: It is unlikely but possible that there is a selector overlap /// with 'transferFrom'. Any integrating contracts should be checked /// for that, as it may result in an unexpected call from this address. /// @param _target The target token to sweep. function sweep(IERC20 _target) external; }
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; import { IMultiTokenEvents } from "./IMultiTokenEvents.sol"; interface IHyperdriveEvents is IMultiTokenEvents { /// @notice Emitted when the Hyperdrive pool is initialized. event Initialize( address indexed provider, uint256 lpAmount, uint256 amount, uint256 vaultSharePrice, bool asBase, uint256 apr, bytes extraData ); /// @notice Emitted when an LP adds liquidity to the Hyperdrive pool. event AddLiquidity( address indexed provider, uint256 lpAmount, uint256 amount, uint256 vaultSharePrice, bool asBase, uint256 lpSharePrice, bytes extraData ); /// @notice Emitted when an LP removes liquidity from the Hyperdrive pool. event RemoveLiquidity( address indexed provider, address indexed destination, uint256 lpAmount, uint256 amount, uint256 vaultSharePrice, bool asBase, uint256 withdrawalShareAmount, uint256 lpSharePrice, bytes extraData ); /// @notice Emitted when an LP redeems withdrawal shares. event RedeemWithdrawalShares( address indexed provider, address indexed destination, uint256 withdrawalShareAmount, uint256 amount, uint256 vaultSharePrice, bool asBase, bytes extraData ); /// @notice Emitted when a long position is opened. event OpenLong( address indexed trader, uint256 indexed assetId, uint256 maturityTime, uint256 amount, uint256 vaultSharePrice, bool asBase, uint256 bondAmount, bytes extraData ); /// @notice Emitted when a short position is opened. event OpenShort( address indexed trader, uint256 indexed assetId, uint256 maturityTime, uint256 amount, uint256 vaultSharePrice, bool asBase, uint256 baseProceeds, uint256 bondAmount, bytes extraData ); /// @notice Emitted when a long position is closed. event CloseLong( address indexed trader, address indexed destination, uint256 indexed assetId, uint256 maturityTime, uint256 amount, uint256 vaultSharePrice, bool asBase, uint256 bondAmount, bytes extraData ); /// @notice Emitted when a short position is closed. event CloseShort( address indexed trader, address indexed destination, uint256 indexed assetId, uint256 maturityTime, uint256 amount, uint256 vaultSharePrice, bool asBase, uint256 basePayment, uint256 bondAmount, bytes extraData ); /// @notice Emitted when a checkpoint is created. event CreateCheckpoint( uint256 indexed checkpointTime, uint256 checkpointVaultSharePrice, uint256 vaultSharePrice, uint256 maturedShorts, uint256 maturedLongs, uint256 lpSharePrice ); /// @notice Emitted when governance fees are collected. event CollectGovernanceFee( address indexed collector, uint256 amount, uint256 vaultSharePrice, bool asBase ); /// @notice Emitted when the pause status is updated. event PauseStatusUpdated(bool isPaused); /// @notice Emitted when tokens are swept. event Sweep(address indexed collector, address indexed target); }
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; import { IHyperdrive } from "./IHyperdrive.sol"; import { IMultiTokenRead } from "./IMultiTokenRead.sol"; interface IHyperdriveRead is IMultiTokenRead { /// @notice Gets the instance's name. /// @return The instance's name. function name() external view returns (string memory); /// @notice Gets the instance's kind. /// @return The instance's kind. function kind() external pure returns (string memory); /// @notice Gets the instance's version. /// @return The instance's version. function version() external pure returns (string memory); /// @notice Gets the address that contains the admin configuration for this /// instance. /// @return The admin controller address. function adminController() external view returns (address); /// @notice Gets the Hyperdrive pool's base token. /// @return The base token. function baseToken() external view returns (address); /// @notice Gets the Hyperdrive pool's vault shares token. /// @return The vault shares token. function vaultSharesToken() external view returns (address); /// @notice Gets one of the pool's checkpoints. /// @param _checkpointTime The checkpoint time. /// @return The checkpoint. function getCheckpoint( uint256 _checkpointTime ) external view returns (IHyperdrive.Checkpoint memory); /// @notice Gets the pool's exposure from a checkpoint. This is the number /// of non-netted longs in the checkpoint. /// @param _checkpointTime The checkpoint time. /// @return The checkpoint exposure. function getCheckpointExposure( uint256 _checkpointTime ) external view returns (int256); /// @notice Gets the pool's state relating to the Hyperdrive market. /// @return The market state. function getMarketState() external view returns (IHyperdrive.MarketState memory); /// @notice Gets the pool's configuration parameters. /// @return The pool configuration. function getPoolConfig() external view returns (IHyperdrive.PoolConfig memory); /// @notice Gets info about the pool's reserves and other state that is /// important to evaluate potential trades. /// @return The pool info. function getPoolInfo() external view returns (IHyperdrive.PoolInfo memory); /// @notice Gets the amount of governance fees that haven't been collected. /// @return The amount of uncollected governance fees. function getUncollectedGovernanceFees() external view returns (uint256); /// @notice Gets information relating to the pool's withdrawal pool. This /// includes the total proceeds underlying the withdrawal pool and /// the number of withdrawal shares ready to be redeemed. /// @return The withdrawal pool information. function getWithdrawPool() external view returns (IHyperdrive.WithdrawPool memory); /// @notice Gets an account's pauser status within the Hyperdrive pool. /// @param _account The account to check. /// @return The account's pauser status. function isPauser(address _account) external view returns (bool); /// @notice Gets the storage values at the specified slots. /// @dev This serves as a generalized getter that allows consumers to create /// custom getters to suit their purposes. /// @param _slots The storage slots to load. /// @return The values at the specified slots. function load( uint256[] calldata _slots ) external view returns (bytes32[] memory); /// @notice Convert an amount of vault shares to an amount of base. /// @dev This is a convenience method that allows developers to convert from /// vault shares to base without knowing the specifics of the /// integration. /// @param _shareAmount The vault shares amount. /// @return baseAmount The base amount. function convertToBase( uint256 _shareAmount ) external view returns (uint256); /// @notice Convert an amount of base to an amount of vault shares. /// @dev This is a convenience method that allows developers to convert from /// base to vault shares without knowing the specifics of the /// integration. /// @param _baseAmount The base amount. /// @return shareAmount The vault shares amount. function convertToShares( uint256 _baseAmount ) external view returns (uint256); /// @notice Gets the total amount of vault shares held by Hyperdrive. /// @dev This is a convenience method that allows developers to get the /// total amount of vault shares without knowing the specifics of the /// integration. /// @return The total amount of vault shares held by Hyperdrive. function totalShares() external view returns (uint256); }
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; interface IHyperdriveRegistry { /// @dev The info collected for each Hyperdrive factory. struct FactoryInfo { /// @dev Data about the factory. Different registries can utilize /// different schemas for these values. uint256 data; } /// @dev The info collected for each Hyperdrive factory along with the /// metadata associated with each instance. struct FactoryInfoWithMetadata { /// @dev Data about the factory. Different registries can utilize /// different schemas for these values. uint256 data; /// @dev The factory's name. string name; /// @dev The factory's kind. string kind; /// @dev The factory's version. string version; } /// @dev The info related to each Hyperdrive instance. struct InstanceInfo { /// @dev Data about the instance. Different registries can utilize /// different schemas for these values. uint256 data; /// @dev The factory that deployed this instance. address factory; } /// @dev The info related to each Hyperdrive instance along with the /// metadata associated with each instance. struct InstanceInfoWithMetadata { /// @dev Data about the instance. Different registries can utilize /// different schemas for these values. uint256 data; /// @dev The factory that deployed this instance. address factory; /// @dev The instance's name. string name; /// @dev The instance's kind. string kind; /// @dev The instance's version. string version; } /// @notice Gets the registry's name. /// @return The registry's name. function name() external view returns (string memory); /// @notice Gets the registry's kind. /// @return The registry's kind. function kind() external pure returns (string memory); /// @notice Gets the registry's version. /// @return The registry's version. function version() external pure returns (string memory); /// @notice Gets the number of Hyperdrive factories that have been registered. /// @return The number of registered factories. function getNumberOfFactories() external view returns (uint256); /// @notice Gets the registered factory at an index. /// @param _index The index of the factory. /// @return The registered factory. function getFactoryAtIndex(uint256 _index) external view returns (address); /// @notice Gets the registered factories in the range of the provided /// indices. /// @param _startIndex The start of the range (inclusive). /// @param _endIndex The end of the range (exclusive). /// @return The list of registered factories in the range. function getFactoriesInRange( uint256 _startIndex, uint256 _endIndex ) external view returns (address[] memory); /// @notice Gets the Hyperdrive factory info for a factory. /// @param _factory The factory. /// @return The factory info. function getFactoryInfo( address _factory ) external view returns (FactoryInfo memory); /// @notice Gets the Hyperdrive factory info for a list of factories. /// @param __factories The list of factories. /// @return The list of factory info. function getFactoryInfos( address[] calldata __factories ) external view returns (FactoryInfo[] memory); /// @notice Gets the Hyperdrive factory info with associated metadata for a /// factory. /// @param _factory The factory. /// @return The factory info with associated metadata. function getFactoryInfoWithMetadata( address _factory ) external view returns (FactoryInfoWithMetadata memory); /// @notice Gets the Hyperdrive factory info with associated metadata for a /// list of factories. /// @param __factories The list of factories. /// @return The list of factory info with associated metadata. function getFactoryInfosWithMetadata( address[] calldata __factories ) external view returns (FactoryInfoWithMetadata[] memory); /// @notice Gets the number of Hyperdrive instances that have been registered. /// @return The number of registered instances. function getNumberOfInstances() external view returns (uint256); /// @notice Gets the registered instance at an index. /// @param _index The index of the instance. /// @return The registered instance. function getInstanceAtIndex(uint256 _index) external view returns (address); /// @notice Gets the registered instances in the range of the provided /// indices. /// @param _startIndex The start of the range (inclusive). /// @param _endIndex The end of the range (exclusive). /// @return The list of registered instances in the range. function getInstancesInRange( uint256 _startIndex, uint256 _endIndex ) external view returns (address[] memory); /// @notice Gets the instance info for an instance. /// @param _instance The instance. /// @return The instance info. function getInstanceInfo( address _instance ) external view returns (InstanceInfo memory); /// @notice Gets the instance info for a list of instances. /// @param __instances The list of instances. /// @return The list of instance info. function getInstanceInfos( address[] calldata __instances ) external view returns (InstanceInfo[] memory); /// @notice Gets the instance info with associated metadata for an instance. /// @param _instance The instance. /// @return The instance info with associated metadata. function getInstanceInfoWithMetadata( address _instance ) external view returns (InstanceInfoWithMetadata memory); /// @notice Gets the instance info with associated metadata for a list of /// instances. /// @param __instances The list of instances. /// @return The list of instance info with associated metadata. function getInstanceInfosWithMetadata( address[] calldata __instances ) external view returns (InstanceInfoWithMetadata[] memory); }
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; import { IHyperdrive } from "./IHyperdrive.sol"; import { IHyperdriveAdminController } from "./IHyperdriveAdminController.sol"; interface IHyperdriveTargetDeployer { /// @notice Deploys a target instance with the given parameters. /// @param _config The configuration of the Hyperdrive pool. /// @param _adminController The admin controller that will specify the /// admin parameters for this instance. /// @param _extraData The extra data that contains the pool and sweep targets. /// @param _salt The create2 salt used in the deployment. /// @return The address of the newly deployed target instance. function deployTarget( IHyperdrive.PoolConfig memory _config, IHyperdriveAdminController _adminController, bytes memory _extraData, bytes32 _salt ) external returns (address); }
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; import { Id, IMorpho } from "morpho-blue/src/interfaces/IMorpho.sol"; import { IHyperdrive } from "./IHyperdrive.sol"; interface IMorphoBlueHyperdrive is IHyperdrive { struct MorphoBlueParams { IMorpho morpho; address collateralToken; address oracle; address irm; uint256 lltv; } /// @notice Gets the vault used as this pool's yield source. /// @return The compatible yield source. function vault() external view returns (address); /// @notice Returns the collateral token for this Morpho Blue market. /// @return The collateral token for this Morpho Blue market. function collateralToken() external view returns (address); /// @notice Returns the oracle for this Morpho Blue market. /// @return The oracle for this Morpho Blue market. function oracle() external view returns (address); /// @notice Returns the interest rate model for this Morpho Blue market. /// @return The interest rate model for this Morpho Blue market. function irm() external view returns (address); /// @notice Returns the liquidation loan to value ratio for this Morpho Blue /// market. /// @return The liquiditation loan to value ratio for this Morpho Blue market. function lltv() external view returns (uint256); /// @notice Returns the Morpho Blue ID for this market. /// @return The Morpho Blue ID. function id() external view returns (Id); }
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; import { IMultiTokenCore } from "./IMultiTokenCore.sol"; import { IMultiTokenEvents } from "./IMultiTokenEvents.sol"; import { IMultiTokenMetadata } from "./IMultiTokenMetadata.sol"; import { IMultiTokenRead } from "./IMultiTokenRead.sol"; interface IMultiToken is IMultiTokenEvents, IMultiTokenRead, IMultiTokenCore, IMultiTokenMetadata {}
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; interface IMultiTokenCore { /// @notice Transfers an amount of assets from the source to the destination. /// @param tokenID The token identifier. /// @param from The address whose balance will be reduced. /// @param to The address whose balance will be increased. /// @param amount The amount of token to move. function transferFrom( uint256 tokenID, address from, address to, uint256 amount ) external; /// @notice Permissioned transfer for the bridge to access, only callable by /// the ERC20 linking bridge. /// @param tokenID The token identifier. /// @param from The address whose balance will be reduced. /// @param to The address whose balance will be increased. /// @param amount The amount of token to move. /// @param caller The msg.sender or the caller of the ERC20Forwarder. function transferFromBridge( uint256 tokenID, address from, address to, uint256 amount, address caller ) external; /// @notice Allows a user to set an approval for an individual asset with /// specific amount. /// @param tokenID The asset to approve the use of. /// @param operator The address who will be able to use the tokens. /// @param amount The max tokens the approved person can use, setting to /// uint256.max will cause the value to never decrement (saving gas /// on transfer). function setApproval( uint256 tokenID, address operator, uint256 amount ) external; /// @notice Allows the compatibility linking contract to forward calls to /// set asset approvals. /// @param tokenID The asset to approve the use of. /// @param operator The address who will be able to use the tokens. /// @param amount The max tokens the approved person can use, setting to /// uint256.max will cause the value to never decrement [saving gas /// on transfer]. /// @param caller The eth address which called the linking contract. function setApprovalBridge( uint256 tokenID, address operator, uint256 amount, address caller ) external; /// @notice Allows a user to approve an operator to use all of their assets. /// @param operator The eth address which can access the caller's assets. /// @param approved True to approve, false to remove approval. function setApprovalForAll(address operator, bool approved) external; /// @notice Transfers several assets from one account to another. /// @param from The source account. /// @param to The destination account. /// @param ids The array of token ids of the asset to transfer. /// @param values The amount of each token to transfer. function batchTransferFrom( address from, address to, uint256[] calldata ids, uint256[] calldata values ) external; /// @notice Allows a caller who is not the owner of an account to execute the /// functionality of 'approve' for all assets with the owner's /// signature. /// @param owner The owner of the account which is having the new approval set. /// @param spender The address which will be allowed to spend owner's tokens. /// @param _approved A boolean of the approval status to set to. /// @param deadline The timestamp which the signature must be submitted by /// to be valid. /// @param v Extra ECDSA data which allows public key recovery from /// signature assumed to be 27 or 28. /// @param r The r component of the ECDSA signature. /// @param s The s component of the ECDSA signature. /// @dev The signature for this function follows EIP 712 standard and should /// be generated with the eth_signTypedData JSON RPC call instead of /// the eth_sign JSON RPC call. If using out of date parity signing /// libraries the v component may need to be adjusted. Also it is very /// rare but possible for v to be other values, those values are not /// supported. function permitForAll( address owner, address spender, bool _approved, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) external; }
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; interface IMultiTokenEvents { /// @notice Emitted when tokens are transferred from one account to another. event TransferSingle( address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value ); /// @notice Emitted when an account changes the allowance for another /// account. event Approval( address indexed owner, address indexed spender, uint256 value ); /// @notice Emitted when an account changes the approval for all of its /// tokens. event ApprovalForAll( address indexed account, address indexed operator, bool approved ); }
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; interface IMultiTokenMetadata { /// @notice Gets the EIP712 permit typehash of the MultiToken. /// @return The EIP712 permit typehash of the MultiToken. // solhint-disable func-name-mixedcase function PERMIT_TYPEHASH() external view returns (bytes32); /// @notice Gets the EIP712 domain separator of the MultiToken. /// @return The EIP712 domain separator of the MultiToken. function domainSeparator() external view returns (bytes32); }
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; interface IMultiTokenRead { /// @notice Gets the decimals of the MultiToken. /// @return The decimals of the MultiToken. function decimals() external view returns (uint8); /// @notice Gets the name of the MultiToken. /// @param tokenId The sub-token ID. /// @return The name of the MultiToken. function name(uint256 tokenId) external view returns (string memory); /// @notice Gets the symbol of the MultiToken. /// @param tokenId The sub-token ID. /// @return The symbol of the MultiToken. function symbol(uint256 tokenId) external view returns (string memory); /// @notice Gets the total supply of the MultiToken. /// @param tokenId The sub-token ID. /// @return The total supply of the MultiToken. function totalSupply(uint256 tokenId) external view returns (uint256); /// @notice Gets the approval-for-all status of a spender on behalf of an /// owner. /// @param owner The owner of the tokens. /// @param spender The spender of the tokens. /// @return The approval-for-all status of the spender for the owner. function isApprovedForAll( address owner, address spender ) external view returns (bool); /// @notice Gets the allowance of a spender for a sub-token. /// @param tokenId The sub-token ID. /// @param owner The owner of the tokens. /// @param spender The spender of the tokens. /// @return The allowance of the spender for the owner. function perTokenApprovals( uint256 tokenId, address owner, address spender ) external view returns (uint256); /// @notice Gets the balance of a spender for a sub-token. /// @param tokenId The sub-token ID. /// @param owner The owner of the tokens. /// @return The balance of the owner. function balanceOf( uint256 tokenId, address owner ) external view returns (uint256); /// @notice Gets the permit nonce for an account. /// @param owner The owner of the tokens. /// @return The permit nonce of the owner. function nonces(address owner) external view returns (uint256); }
// SPDX-License-Identifier: Apache-2.0 pragma solidity 0.8.22; import { ERC20 } from "openzeppelin/token/ERC20/ERC20.sol"; import { SafeERC20 } from "openzeppelin/token/ERC20/utils/SafeERC20.sol"; import { IERC20 } from "../interfaces/IERC20.sol"; import { IHyperdrive } from "../interfaces/IHyperdrive.sol"; import { IHyperdriveEvents } from "../interfaces/IHyperdriveEvents.sol"; import { HyperdriveBase } from "./HyperdriveBase.sol"; /// @author DELV /// @title HyperdriveAdmin /// @notice The Hyperdrive admin contract. This contract provides functions that /// governance can use to pause the pool and update permissions. /// @custom:disclaimer The language used in this code is for coding convenience /// only, and is not intended to, and does not, have any /// particular legal or regulatory significance. abstract contract HyperdriveAdmin is IHyperdriveEvents, HyperdriveBase { using SafeERC20 for ERC20; /// @dev This function collects the governance fees accrued by the pool. /// @param _options The options that configure how the fees are settled. /// @return proceeds The governance fees collected. The units of this /// quantity are either base or vault shares, depending on the value /// of `_options.asBase`. function _collectGovernanceFee( IHyperdrive.Options calldata _options ) internal nonReentrant returns (uint256 proceeds) { // Check that the provided options are valid. _checkOptions(_options); // Ensure that the destination is set to the fee collector. address feeCollector = _adminController.feeCollector(); if (_options.destination != feeCollector) { revert IHyperdrive.InvalidFeeDestination(); } // Ensure that the caller is authorized to collect fees. if ( msg.sender != feeCollector && msg.sender != _adminController.hyperdriveGovernance() && !_isPauser(msg.sender) ) { revert IHyperdrive.Unauthorized(); } // Withdraw the accrued governance fees to the fee collector. uint256 vaultSharePrice = _pricePerVaultShare(); uint256 governanceFeesAccrued = _governanceFeesAccrued; delete _governanceFeesAccrued; proceeds = _withdraw(governanceFeesAccrued, vaultSharePrice, _options); emit CollectGovernanceFee( feeCollector, proceeds, vaultSharePrice, _options.asBase ); } /// @dev Allows an authorized address to pause this contract. /// @param _status True to pause all deposits and false to unpause them. function _pause(bool _status) internal { // Ensure that the sender is authorized to pause the contract. if ( msg.sender != _adminController.hyperdriveGovernance() && !_isPauser(msg.sender) ) { revert IHyperdrive.Unauthorized(); } // Update the paused status and emit an event. _marketState.isPaused = _status; emit PauseStatusUpdated(_status); } /// @dev Transfers the contract's balance of a target token to the sweep /// collector address. /// @dev WARN: It is unlikely but possible that there is a selector overlap /// with 'transfer'. Any integrating contracts should be checked /// for that, as it may result in an unexpected call from this address. /// @param _target The target token to sweep. function _sweep(IERC20 _target) internal nonReentrant { // Ensure that the caller is authorized to sweep tokens. address sweepCollector = _adminController.sweepCollector(); if ( msg.sender != sweepCollector && msg.sender != _adminController.hyperdriveGovernance() && !_isPauser(msg.sender) ) { revert IHyperdrive.Unauthorized(); } // Gets the Hyperdrive's balance of vault shares prior to // sweeping. uint256 shareBalance = _totalShares(); // Transfer the entire balance of the sweep target to the sweep // collector. uint256 balance = _target.balanceOf(address(this)); ERC20(address(_target)).safeTransfer(sweepCollector, balance); // Ensure that the vault shares balance hasn't changed. if (_totalShares() != shareBalance) { revert IHyperdrive.SweepFailed(); } emit Sweep(sweepCollector, address(_target)); } /// @dev Gets an account's pauser status within the Hyperdrive pool. /// @param _account The account to check. /// @return The account's pauser status. function _isPauser(address _account) internal view returns (bool) { address[] memory pausers = _adminController.defaultPausers(); for (uint256 i = 0; i < pausers.length; i++) { if (pausers[i] == _account) { return true; } } return false; } }
// SPDX-License-Identifier: Apache-2.0 pragma solidity 0.8.22; import { IHyperdrive } from "../interfaces/IHyperdrive.sol"; import { IHyperdriveEvents } from "../interfaces/IHyperdriveEvents.sol"; import { AssetId } from "../libraries/AssetId.sol"; import { FixedPointMath, ONE } from "../libraries/FixedPointMath.sol"; import { HyperdriveMath } from "../libraries/HyperdriveMath.sol"; import { LPMath } from "../libraries/LPMath.sol"; import { YieldSpaceMath } from "../libraries/YieldSpaceMath.sol"; import { SafeCast } from "../libraries/SafeCast.sol"; import { HyperdriveStorage } from "./HyperdriveStorage.sol"; /// @author DELV /// @title HyperdriveBase /// @notice The Hyperdrive base contract that provides a set of helper methods /// and defines the functions that must be overridden by implementations. /// @custom:disclaimer The language used in this code is for coding convenience /// only, and is not intended to, and does not, have any /// particular legal or regulatory significance. abstract contract HyperdriveBase is IHyperdriveEvents, HyperdriveStorage { using FixedPointMath for uint256; using FixedPointMath for int256; using SafeCast for uint256; using SafeCast for int256; /// Yield Source /// /// @dev Process a deposit in either base or vault shares. /// @param _amount The amount of capital to deposit. The units of this /// quantity are either base or vault shares, depending on the value /// of `_options.asBase`. /// @param _options The options that configure how the deposit is /// settled. In particular, the currency used in the deposit is /// specified here. Aside from those options, yield sources can /// choose to implement additional options. /// @return sharesMinted The shares created by this deposit. /// @return vaultSharePrice The vault share price. function _deposit( uint256 _amount, IHyperdrive.Options calldata _options ) internal returns (uint256 sharesMinted, uint256 vaultSharePrice) { // WARN: This logic doesn't account for slippage in the conversion // from base to shares. If deposits to the yield source incur // slippage, this logic will be incorrect. // // The amount of shares minted is equal to the input amount if the // deposit asset is in shares. sharesMinted = _amount; // Deposit with either base or shares depending on the provided options. uint256 refund; if (_options.asBase) { // Process the deposit in base. (sharesMinted, refund) = _depositWithBase( _amount, _options.extraData ); } else { // The refund is equal to the full message value since ETH will // never be a shares asset. refund = msg.value; // Process the deposit in shares. _depositWithShares(_amount, _options.extraData); } // Calculate the vault share price. vaultSharePrice = _pricePerVaultShare(); // Return excess ether that was sent to the contract. if (refund > 0) { (bool success, ) = payable(msg.sender).call{ value: refund }(""); if (!success) { revert IHyperdrive.TransferFailed(); } } return (sharesMinted, vaultSharePrice); } /// @dev Process a withdrawal and send the proceeds to the destination. /// @param _shares The vault shares to withdraw from the yield source. /// @param _vaultSharePrice The vault share price. /// @param _options The options that configure how the withdrawal is /// settled. In particular, the destination and currency used in the /// withdrawal are specified here. Aside from those options, yield /// sources can choose to implement additional options. /// @return amountWithdrawn The proceeds of the withdrawal. The units of /// this quantity are either base or vault shares, depending on the /// value of `_options.asBase`. function _withdraw( uint256 _shares, uint256 _vaultSharePrice, IHyperdrive.Options calldata _options ) internal returns (uint256 amountWithdrawn) { // NOTE: Round down to underestimate the base proceeds. // // Correct for any error that crept into the calculation of the share // amount by converting the shares to base and then back to shares // using the vault's share conversion logic. uint256 baseAmount = _shares.mulDown(_vaultSharePrice); _shares = _convertToShares(baseAmount); // If we're withdrawing zero shares, short circuit and return 0. if (_shares == 0) { return 0; } // Withdraw in either base or shares depending on the provided options. amountWithdrawn = _shares; if (_options.asBase) { // Process the withdrawal in base. amountWithdrawn = _withdrawWithBase( _shares, _options.destination, _options.extraData ); } else { // Process the withdrawal in shares. _withdrawWithShares( _shares, _options.destination, _options.extraData ); } return amountWithdrawn; } /// @dev Loads the share price from the yield source. /// @return vaultSharePrice The current vault share price. function _pricePerVaultShare() internal view returns (uint256 vaultSharePrice) { return _convertToBase(ONE); } /// @dev Accepts a deposit from the user in base. /// @param _baseAmount The base amount to deposit. /// @param _extraData The extra data to use in the deposit. /// @return sharesMinted The shares that were minted in the deposit. /// @return refund The amount of ETH to refund. This should be zero for /// yield sources that don't accept ETH. function _depositWithBase( uint256 _baseAmount, bytes calldata _extraData ) internal virtual returns (uint256 sharesMinted, uint256 refund); /// @dev Process a deposit in vault shares. /// @param _shareAmount The vault shares amount to deposit. /// @param _extraData The extra data to use in the deposit. function _depositWithShares( uint256 _shareAmount, bytes calldata _extraData ) internal virtual; /// @dev Process a withdrawal in base and send the proceeds to the /// destination. /// @param _shareAmount The amount of vault shares to withdraw. /// @param _destination The destination of the withdrawal. /// @param _extraData The extra data used to settle the withdrawal. /// @return amountWithdrawn The amount of base withdrawn. function _withdrawWithBase( uint256 _shareAmount, address _destination, bytes calldata _extraData ) internal virtual returns (uint256 amountWithdrawn); /// @dev Process a withdrawal in vault shares and send the proceeds to the /// destination. /// @param _shareAmount The amount of vault shares to withdraw. /// @param _destination The destination of the withdrawal. /// @param _extraData The extra data used to settle the withdrawal. function _withdrawWithShares( uint256 _shareAmount, address _destination, bytes calldata _extraData ) internal virtual; /// @dev A yield source dependent check that prevents ether from being /// transferred to Hyperdrive instances that don't accept ether. function _checkMessageValue() internal view virtual; /// @dev A yield source dependent check that verifies that the provided /// options are valid. The default check is that the destination is /// non-zero to prevent users from accidentally transferring funds /// to the zero address. Custom integrations can override this to /// implement additional checks. /// @param _options The provided options for the transaction. function _checkOptions( IHyperdrive.Options calldata _options ) internal pure virtual { if (_options.destination == address(0)) { revert IHyperdrive.RestrictedZeroAddress(); } } /// @dev Convert an amount of vault shares to an amount of base. /// @param _shareAmount The vault shares amount. /// @return baseAmount The base amount. function _convertToBase( uint256 _shareAmount ) internal view virtual returns (uint256 baseAmount); /// @dev Convert an amount of base to an amount of vault shares. /// @param _baseAmount The base amount. /// @return shareAmount The vault shares amount. function _convertToShares( uint256 _baseAmount ) internal view virtual returns (uint256 shareAmount); /// @dev Gets the total amount of shares held by the pool in the yield /// source. /// @return shareAmount The total amount of shares. function _totalShares() internal view virtual returns (uint256 shareAmount); /// Pause /// /// @dev Blocks a function execution if the contract is paused. modifier isNotPaused() { if (_marketState.isPaused) { revert IHyperdrive.PoolIsPaused(); } _; } /// Checkpoint /// /// @dev Creates a new checkpoint if necessary. /// @param _checkpointTime The time of the checkpoint to create. /// @param _vaultSharePrice The current vault share price. /// @param _maxIterations The number of iterations to use in the Newton's /// method component of `_distributeExcessIdleSafe`. This defaults to /// `LPMath.SHARE_PROCEEDS_MAX_ITERATIONS` if the specified value is /// smaller than the constant. /// @param _isTrader A boolean indicating whether or not the checkpoint was /// minted by a trader or by someone calling checkpoint directly. /// @return openVaultSharePrice The open vault share price of the latest /// checkpoint. function _applyCheckpoint( uint256 _checkpointTime, uint256 _vaultSharePrice, uint256 _maxIterations, bool _isTrader ) internal virtual returns (uint256 openVaultSharePrice); /// Helpers /// /// @dev Calculates the normalized time remaining of a position. /// @param _maturityTime The maturity time of the position. /// @return timeRemaining The normalized time remaining (in [0, 1]). function _calculateTimeRemaining( uint256 _maturityTime ) internal view returns (uint256 timeRemaining) { uint256 latestCheckpoint = _latestCheckpoint(); timeRemaining = _maturityTime > latestCheckpoint ? _maturityTime - latestCheckpoint : 0; // NOTE: Round down to underestimate the time remaining. timeRemaining = timeRemaining.divDown(_positionDuration); } /// @dev Calculates the normalized time remaining of a position when the /// maturity time is scaled up 18 decimals. /// @param _maturityTime The maturity time of the position. function _calculateTimeRemainingScaled( uint256 _maturityTime ) internal view returns (uint256 timeRemaining) { uint256 latestCheckpoint = _latestCheckpoint() * ONE; timeRemaining = _maturityTime > latestCheckpoint ? _maturityTime - latestCheckpoint : 0; // NOTE: Round down to underestimate the time remaining. timeRemaining = timeRemaining.divDown(_positionDuration * ONE); } /// @dev Gets the most recent checkpoint time. /// @return latestCheckpoint The latest checkpoint. function _latestCheckpoint() internal view returns (uint256 latestCheckpoint) { latestCheckpoint = HyperdriveMath.calculateCheckpointTime( block.timestamp, _checkpointDuration ); } /// @dev Gets the effective share reserves. /// @return The effective share reserves. This is the share reserves used /// by the YieldSpace pricing model. function _effectiveShareReserves() internal view returns (uint256) { return HyperdriveMath.calculateEffectiveShareReserves( _marketState.shareReserves, _marketState.shareAdjustment ); } /// @dev Gets the amount of non-netted longs with a given maturity. /// @param _maturityTime The maturity time of the longs. /// @return The amount of non-netted longs. This is a signed value that /// can be negative. This is convenient for updating the long /// exposure when closing positions. function _nonNettedLongs( uint256 _maturityTime ) internal view returns (int256) { // The amount of non-netted longs is the difference between the amount // of longs and the amount of shorts with a given maturity time. If the // difference is negative, the amount of non-netted longs is zero. return _totalSupply[ AssetId.encodeAssetId(AssetId.AssetIdPrefix.Long, _maturityTime) ].toInt256() - _totalSupply[ AssetId.encodeAssetId( AssetId.AssetIdPrefix.Short, _maturityTime ) ].toInt256(); } /// @dev Gets the distribute excess idle parameters from the current state. /// @param _vaultSharePrice The current vault share price. /// @return params The distribute excess idle parameters. /// @return success A failure flag indicating if the calculation succeeded. function _getDistributeExcessIdleParamsSafe( uint256 _idle, uint256 _withdrawalSharesTotalSupply, uint256 _vaultSharePrice ) internal view returns (LPMath.DistributeExcessIdleParams memory params, bool success) { // Calculate the starting present value. If this fails, we return a // failure flag and proceed to avoid impacting checkpointing liveness. LPMath.PresentValueParams memory presentValueParams = _getPresentValueParams( _vaultSharePrice ); uint256 startingPresentValue; (startingPresentValue, success) = LPMath.calculatePresentValueSafe( presentValueParams ); if (!success) { return (params, false); } // NOTE: For consistency with the present value calculation, we round // up the long side and round down the short side. int256 netCurveTrade = presentValueParams .longsOutstanding .mulUp(presentValueParams.longAverageTimeRemaining) .toInt256() - presentValueParams .shortsOutstanding .mulDown(presentValueParams.shortAverageTimeRemaining) .toInt256(); params = LPMath.DistributeExcessIdleParams({ presentValueParams: presentValueParams, startingPresentValue: startingPresentValue, activeLpTotalSupply: _totalSupply[AssetId._LP_ASSET_ID], withdrawalSharesTotalSupply: _withdrawalSharesTotalSupply, idle: _idle, netCurveTrade: netCurveTrade, originalShareReserves: presentValueParams.shareReserves, originalShareAdjustment: presentValueParams.shareAdjustment, originalBondReserves: presentValueParams.bondReserves }); success = true; } /// @dev Gets the present value parameters from the current state. /// @param _vaultSharePrice The current vault share price. /// @return params The present value parameters. function _getPresentValueParams( uint256 _vaultSharePrice ) internal view returns (LPMath.PresentValueParams memory params) { params = LPMath.PresentValueParams({ shareReserves: _marketState.shareReserves, shareAdjustment: _marketState.shareAdjustment, bondReserves: _marketState.bondReserves, vaultSharePrice: _vaultSharePrice, initialVaultSharePrice: _initialVaultSharePrice, minimumShareReserves: _minimumShareReserves, minimumTransactionAmount: _minimumTransactionAmount, timeStretch: _timeStretch, longsOutstanding: _marketState.longsOutstanding, longAverageTimeRemaining: _calculateTimeRemainingScaled( _marketState.longAverageMaturityTime ), shortsOutstanding: _marketState.shortsOutstanding, shortAverageTimeRemaining: _calculateTimeRemainingScaled( _marketState.shortAverageMaturityTime ) }); } /// @dev Checks if any of the bonds the trader purchased on the curve /// were purchased above the price of 1 base per bonds. /// @param _shareCurveDelta The amount of shares the trader pays the curve. /// @param _bondCurveDelta The amount of bonds the trader receives from the /// curve. /// @param _maxSpotPrice The maximum allowable spot price for the trade. /// @return A flag indicating whether the trade was negative interest. function _isNegativeInterest( uint256 _shareCurveDelta, uint256 _bondCurveDelta, uint256 _maxSpotPrice ) internal view returns (bool) { // Calculate the spot price after making the trade on the curve but // before accounting for fees. Compare this to the max spot price to // determine if the trade is negative interest. uint256 endingSpotPrice = HyperdriveMath.calculateSpotPrice( _effectiveShareReserves() + _shareCurveDelta, _marketState.bondReserves - _bondCurveDelta, _initialVaultSharePrice, _timeStretch ); return endingSpotPrice > _maxSpotPrice; } /// @dev Check solvency by verifying that the share reserves are greater /// than the exposure plus the minimum share reserves. /// @param _vaultSharePrice The current vault share price. /// @return True if the share reserves are greater than the exposure plus /// the minimum share reserves. function _isSolvent(uint256 _vaultSharePrice) internal view returns (bool) { // NOTE: Round the lhs down and the rhs up to make the check more // conservative. return uint256(_marketState.shareReserves).mulDown(_vaultSharePrice) >= _marketState.longExposure + _minimumShareReserves.mulUp(_vaultSharePrice); } /// @dev Updates the global long exposure. /// @param _before The checkpoint long exposure before the update. /// @param _after The checkpoint long exposure after the update. function _updateLongExposure(int256 _before, int256 _after) internal { _marketState.longExposure = LPMath .calculateLongExposure(_marketState.longExposure, _before, _after) .toUint128(); } /// @dev Update the weighted spot price from a specified checkpoint. The /// weighted spot price is a time weighted average of the spot prices /// in the checkpoint. /// @param _checkpointTime The checkpoint time of the checkpoint to update. /// @param _updateTime The time at which the update is being processed. Most /// of the time, this is the latest block time, but when updating /// past checkpoints, this may be the time at the end of the /// checkpoint. /// @param _spotPrice The spot price to accumulate into the time weighted /// average. function _updateWeightedSpotPrice( uint256 _checkpointTime, uint256 _updateTime, uint256 _spotPrice ) internal { // If the update time is equal to the last update time, the time delta // is zero, so we don't need to update the time weighted average. uint256 lastWeightedSpotPriceUpdateTime = _checkpoints[_checkpointTime] .lastWeightedSpotPriceUpdateTime; if (_updateTime == lastWeightedSpotPriceUpdateTime) { return; } // If the previous weighted spot price is zero, then the weighted spot // price is set to the spot price that is being accumulated. uint256 previousWeightedSpotPrice = _checkpoints[_checkpointTime] .weightedSpotPrice; if (previousWeightedSpotPrice == 0) { _checkpoints[_checkpointTime].weightedSpotPrice = _spotPrice .toUint128(); } // Otherwise the previous weighted spot price is non-zero and the update // time is greater than the latest update time, the we accumulate the // spot price into the weighted spot price. else { _checkpoints[_checkpointTime] .weightedSpotPrice = previousWeightedSpotPrice .updateWeightedAverage( (lastWeightedSpotPriceUpdateTime - _checkpointTime) * ONE, _spotPrice, (_updateTime - lastWeightedSpotPriceUpdateTime) * ONE, true ) .toUint128(); } // Record the update time as the last update time. _checkpoints[_checkpointTime] .lastWeightedSpotPriceUpdateTime = _updateTime.toUint128(); } /// @dev Apply the updates to the market state as a result of closing a /// position after maturity. This function also adjusts the proceeds /// to account for any negative interest that has accrued in the /// zombie reserves. /// @param _shareProceeds The share proceeds. /// @param _vaultSharePrice The current vault share price. /// @return The adjusted share proceeds. function _applyZombieClose( uint256 _shareProceeds, uint256 _vaultSharePrice ) internal returns (uint256) { // Collect any zombie interest that has accrued since the last // collection. ( uint256 zombieBaseProceeds, uint256 zombieBaseReserves ) = _collectZombieInterest(_vaultSharePrice); // NOTE: Round down to underestimate the proceeds. // // If negative interest has accrued in the zombie reserves, we // discount the share proceeds in proportion to the amount of // negative interest that has accrued. uint256 baseProceeds = _shareProceeds.mulDown(_vaultSharePrice); if (zombieBaseProceeds > zombieBaseReserves) { _shareProceeds = _shareProceeds.mulDivDown( zombieBaseReserves, zombieBaseProceeds ); } // Apply the updates to the zombie base proceeds and share reserves. if (baseProceeds < zombieBaseProceeds) { unchecked { zombieBaseProceeds -= baseProceeds; } } else { zombieBaseProceeds = 0; } _marketState.zombieBaseProceeds = zombieBaseProceeds.toUint112(); uint256 zombieShareReserves = _marketState.zombieShareReserves; if (_shareProceeds < zombieShareReserves) { unchecked { zombieShareReserves -= _shareProceeds; } } else { zombieShareReserves = 0; } _marketState.zombieShareReserves = zombieShareReserves.toUint128(); return _shareProceeds; } /// @dev Collect the interest earned on unredeemed matured positions. This /// interest is split between the LPs and governance. /// @param _vaultSharePrice The current vault share price. /// @return zombieBaseProceeds The base proceeds reserved for zombie /// positions. /// @return zombieBaseReserves The updated base reserves reserved for zombie /// positions. function _collectZombieInterest( uint256 _vaultSharePrice ) internal returns (uint256 zombieBaseProceeds, uint256 zombieBaseReserves) { // NOTE: Round down to underestimate the proceeds. // // Get the zombie base proceeds and reserves. zombieBaseReserves = _vaultSharePrice.mulDown( _marketState.zombieShareReserves ); zombieBaseProceeds = _marketState.zombieBaseProceeds; // If the zombie base reserves are greater than the zombie base // proceeds, then there is interest to collect. if (zombieBaseReserves > zombieBaseProceeds) { // The interest collected on the zombie position is simply the // difference between the base reserves and the base proceeds. uint256 zombieInterest = zombieBaseReserves - zombieBaseProceeds; // NOTE: Round up to overestimate the impact that removing the // interest had on the zombie share reserves. // // Remove the zombie interest from the zombie share reserves. _marketState.zombieShareReserves -= zombieInterest .divUp(_vaultSharePrice) .toUint128(); // NOTE: Round down to underestimate the zombie interest given to // the LPs and governance. // // Calculate and collect the governance fee. // The fee is calculated in terms of shares and paid to // governance. uint256 zombieInterestShares = zombieInterest.divDown( _vaultSharePrice ); uint256 governanceZombieFeeCollected = zombieInterestShares.mulDown( _governanceZombieFee ); _governanceFeesAccrued += governanceZombieFeeCollected; // The zombie interest that was collected (minus the fees paid to // governance), are reinvested in the share reserves. The share // adjustment is updated in lock-step to avoid changing the curve's // k invariant. zombieInterestShares -= governanceZombieFeeCollected; _marketState.shareReserves += zombieInterestShares.toUint128(); _marketState.shareAdjustment += zombieInterestShares.toInt128(); // After collecting the interest, the zombie base reserves are // equal to the zombie base proceeds. zombieBaseReserves = zombieBaseProceeds; } } /// @dev Calculates the number of share reserves that are not reserved by /// open positions. /// @param _vaultSharePrice The current vault share price. /// @return idleShares The amount of shares that are available for LPs to /// withdraw. function _calculateIdleShareReserves( uint256 _vaultSharePrice ) internal view returns (uint256 idleShares) { // NOTE: Round up to underestimate the pool's idle. uint256 longExposure = uint256(_marketState.longExposure).divUp( _vaultSharePrice ); if (_marketState.shareReserves > longExposure + _minimumShareReserves) { idleShares = _marketState.shareReserves - longExposure - _minimumShareReserves; } return idleShares; } /// @dev Calculates the LP share price. If the LP share price can't be /// calculated, this function returns a failure flag. /// @param _vaultSharePrice The current vault share price. /// @return The LP share price in units of (base / lp shares). /// @return A flag indicating if the calculation succeeded. function _calculateLPSharePriceSafe( uint256 _vaultSharePrice ) internal view returns (uint256, bool) { // Calculate the present value safely to prevent liveness problems. If // the calculation fails, we return 0. (uint256 presentValueShares, bool success) = LPMath .calculatePresentValueSafe( _getPresentValueParams(_vaultSharePrice) ); if (!success) { return (0, false); } // Calculate the LP total supply. uint256 lpTotalSupply = _totalSupply[AssetId._LP_ASSET_ID] + _totalSupply[AssetId._WITHDRAWAL_SHARE_ASSET_ID] - _withdrawPool.readyToWithdraw; // If the LP total supply is zero, the LP share price can't be computed // due to a divide-by-zero error. if (lpTotalSupply == 0) { return (0, false); } // NOTE: Round down to underestimate the LP share price. // // Calculate the LP share price. uint256 lpSharePrice = _vaultSharePrice > 0 ? presentValueShares.mulDivDown(_vaultSharePrice, lpTotalSupply) : 0; return (lpSharePrice, true); } /// @dev Calculates the pool's solvency if a long is opened that brings the /// rate to 0%. This is the maximum possible long that can be opened on /// the YieldSpace curve. /// @param _shareReserves The pool's share reserves. /// @param _shareAdjustment The pool's share adjustment. /// @param _bondReserves The pool's bond reserves. /// @param _vaultSharePrice The vault share price. /// @param _longExposure The pool's long exposure. /// @param _checkpointExposure The pool's checkpoint exposure. /// @return The solvency after opening the max long. /// @return A flag indicating whether or not the calculation succeeded. function _calculateSolvencyAfterMaxLongSafe( uint256 _shareReserves, int256 _shareAdjustment, uint256 _bondReserves, uint256 _vaultSharePrice, uint256 _longExposure, int256 _checkpointExposure ) internal view returns (int256, bool) { // Calculate the share payment and bond proceeds of opening the largest // possible long on the YieldSpace curve. This does not include fees. // These calculations fail when the max long is close to zero, and we // ignore these failures since we can proceed with the calculation in // this case. (uint256 effectiveShareReserves, bool success) = HyperdriveMath .calculateEffectiveShareReservesSafe( _shareReserves, _shareAdjustment ); if (!success) { return (0, false); } (uint256 maxSharePayment, ) = YieldSpaceMath .calculateMaxBuySharesInSafe( effectiveShareReserves, _bondReserves, ONE - _timeStretch, _vaultSharePrice, _initialVaultSharePrice ); (uint256 maxBondProceeds, ) = YieldSpaceMath .calculateBondsOutGivenSharesInDownSafe( effectiveShareReserves, _bondReserves, maxSharePayment, ONE - _timeStretch, _vaultSharePrice, _initialVaultSharePrice ); // If one of the max share payment or max bond proceeds calculations // fail or return zero, the max long amount is zero plus or minus a few // wei. if (maxSharePayment == 0 || maxBondProceeds == 0) { maxSharePayment = 0; maxBondProceeds = 0; } // Apply the fees from opening a long to the max share payment and bond // proceeds. Fees applied to the share payment hurt solvency and fees // applied to the bond proceeds make the pool more solvent. To be // conservative, we only apply the fee to the share payment. uint256 spotPrice = HyperdriveMath.calculateSpotPrice( effectiveShareReserves, _bondReserves, _initialVaultSharePrice, _timeStretch ); (maxSharePayment, , ) = _calculateOpenLongFees( maxSharePayment, maxBondProceeds, _vaultSharePrice, spotPrice ); // Calculate the pool's solvency after opening the max long. uint256 shareReserves = _shareReserves + maxSharePayment; uint256 longExposure = LPMath.calculateLongExposure( _longExposure, _checkpointExposure, _checkpointExposure + maxBondProceeds.toInt256() ); uint256 vaultSharePrice = _vaultSharePrice; return ( shareReserves.mulDown(vaultSharePrice).toInt256() - longExposure.toInt256() - _minimumShareReserves.mulUp(vaultSharePrice).toInt256(), true ); } /// @dev Calculates the share reserves delta, the bond reserves delta, and /// the total governance fee after opening a long. /// @param _shareReservesDelta The change in the share reserves without fees. /// @param _bondReservesDelta The change in the bond reserves without fees. /// @param _vaultSharePrice The current vault share price. /// @param _spotPrice The current spot price. /// @return The change in the share reserves with fees. /// @return The change in the bond reserves with fees. /// @return The governance fee in shares. function _calculateOpenLongFees( uint256 _shareReservesDelta, uint256 _bondReservesDelta, uint256 _vaultSharePrice, uint256 _spotPrice ) internal view returns (uint256, uint256, uint256) { // Calculate the fees charged to the user (curveFee) and the portion // of those fees that are paid to governance (governanceCurveFee). ( uint256 curveFee, // bonds uint256 governanceCurveFee // bonds ) = _calculateFeesGivenShares( _shareReservesDelta, _spotPrice, _vaultSharePrice ); // Calculate the impact of the curve fee on the bond reserves. The curve // fee benefits the LPs by causing less bonds to be deducted from the // bond reserves. _bondReservesDelta -= curveFee; // NOTE: Round down to underestimate the governance fee. // // Calculate the fees owed to governance in shares. Open longs are // calculated entirely on the curve so the curve fee is the total // governance fee. In order to convert it to shares we need to multiply // it by the spot price and divide it by the vault share price: // // shares = (bonds * base/bonds) / (base/shares) // shares = bonds * shares/bonds // shares = shares uint256 totalGovernanceFee = governanceCurveFee.mulDivDown( _spotPrice, _vaultSharePrice ); // Calculate the number of shares to add to the shareReserves. // shareReservesDelta, _shareAmount and totalGovernanceFee // are all denominated in shares: // // shares = shares - shares _shareReservesDelta -= totalGovernanceFee; return (_shareReservesDelta, _bondReservesDelta, totalGovernanceFee); } /// @dev Calculates the fees that go to the LPs and governance. /// @param _shareAmount The amount of shares exchanged for bonds. /// @param _spotPrice The price without slippage of bonds in terms of base /// (base/bonds). /// @param _vaultSharePrice The current vault share price (base/shares). /// @return curveFee The curve fee. The fee is in terms of bonds. /// @return governanceCurveFee The curve fee that goes to governance. The /// fee is in terms of bonds. function _calculateFeesGivenShares( uint256 _shareAmount, uint256 _spotPrice, uint256 _vaultSharePrice ) internal view returns (uint256 curveFee, uint256 governanceCurveFee) { // NOTE: Round up to overestimate the curve fee. // // Fixed Rate (r) = (value at maturity - purchase price)/(purchase price) // = (1-p)/p // = ((1 / p) - 1) // = the ROI at maturity of a bond purchased at price p // // Another way to think about it: // // p (spot price) tells us how many base a bond is worth -> p = base/bonds // 1/p tells us how many bonds a base is worth -> 1/p = bonds/base // 1/p - 1 tells us how many additional bonds we get for each // base -> (1/p - 1) = additional bonds/base // // The curve fee is taken from the additional bonds the user gets for // each base: // // curve fee = ((1 / p) - 1) * phi_curve * c * dz // = r * phi_curve * base/shares * shares // = bonds/base * phi_curve * base // = bonds * phi_curve curveFee = (ONE.divUp(_spotPrice) - ONE) .mulUp(_curveFee) .mulUp(_vaultSharePrice) .mulUp(_shareAmount); // NOTE: Round down to underestimate the governance curve fee. // // We leave the governance fee in terms of bonds: // governanceCurveFee = curve_fee * phi_gov // = bonds * phi_gov governanceCurveFee = curveFee.mulDown(_governanceLPFee); } /// @dev Calculates the fees that go to the LPs and governance. /// @param _bondAmount The amount of bonds being exchanged for shares. /// @param _normalizedTimeRemaining The normalized amount of time until /// maturity. /// @param _spotPrice The price without slippage of bonds in terms of base /// (base/bonds). /// @param _vaultSharePrice The current vault share price (base/shares). /// @return curveFee The curve fee. The fee is in terms of shares. /// @return flatFee The flat fee. The fee is in terms of shares. /// @return governanceCurveFee The curve fee that goes to governance. The /// fee is in terms of shares. /// @return totalGovernanceFee The total fee that goes to governance. The /// fee is in terms of shares. function _calculateFeesGivenBonds( uint256 _bondAmount, uint256 _normalizedTimeRemaining, uint256 _spotPrice, uint256 _vaultSharePrice ) internal view returns ( uint256 curveFee, uint256 flatFee, uint256 governanceCurveFee, uint256 totalGovernanceFee ) { // NOTE: Round up to overestimate the curve fee. // // p (spot price) tells us how many base a bond is worth -> p = base/bonds // 1 - p tells us how many additional base a bond is worth at // maturity -> (1 - p) = additional base/bonds // // The curve fee is taken from the additional base the user gets for // each bond at maturity: // // curve fee = ((1 - p) * phi_curve * d_y * t)/c // = (base/bonds * phi_curve * bonds * t) / (base/shares) // = (base/bonds * phi_curve * bonds * t) * (shares/base) // = (base * phi_curve * t) * (shares/base) // = phi_curve * t * shares curveFee = _curveFee .mulUp(ONE - _spotPrice) .mulUp(_bondAmount) .mulDivUp(_normalizedTimeRemaining, _vaultSharePrice); // NOTE: Round down to underestimate the governance curve fee. // // Calculate the curve portion of the governance fee: // // governanceCurveFee = curve_fee * phi_gov // = shares * phi_gov governanceCurveFee = curveFee.mulDown(_governanceLPFee); // NOTE: Round up to overestimate the flat fee. // // The flat portion of the fee is taken from the matured bonds. // Since a matured bond is worth 1 base, it is appropriate to consider // d_y in units of base: // // flat fee = (d_y * (1 - t) * phi_flat) / c // = (base * (1 - t) * phi_flat) / (base/shares) // = (base * (1 - t) * phi_flat) * (shares/base) // = shares * (1 - t) * phi_flat uint256 flat = _bondAmount.mulDivUp( ONE - _normalizedTimeRemaining, _vaultSharePrice ); flatFee = flat.mulUp(_flatFee); // NOTE: Round down to underestimate the total governance fee. // // We calculate the flat portion of the governance fee as: // // governance_flat_fee = flat_fee * phi_gov // = shares * phi_gov // // The totalGovernanceFee is the sum of the curve and flat governance fees. totalGovernanceFee = governanceCurveFee + flatFee.mulDown(_governanceLPFee); } /// @dev Converts input to what is specified in the options from base. /// @param _amount The amount to convert. /// @param _vaultSharePrice The current vault share price. /// @param _options The options that configure the conversion. /// @return The converted amount. function _convertToOptionFromBase( uint256 _amount, uint256 _vaultSharePrice, IHyperdrive.Options calldata _options ) internal pure returns (uint256) { if (_options.asBase) { return _amount; } else { // NOTE: Round down to underestimate the shares amount. return _amount.divDown(_vaultSharePrice); } } }
// SPDX-License-Identifier: Apache-2.0 pragma solidity 0.8.22; import { ExcessivelySafeCall } from "nomad/ExcessivelySafeCall.sol"; import { IHyperdrive } from "../interfaces/IHyperdrive.sol"; import { IHyperdriveCheckpointRewarder } from "../interfaces/IHyperdriveCheckpointRewarder.sol"; import { IHyperdriveEvents } from "../interfaces/IHyperdriveEvents.sol"; import { AssetId } from "../libraries/AssetId.sol"; import { FixedPointMath } from "../libraries/FixedPointMath.sol"; import { HyperdriveMath } from "../libraries/HyperdriveMath.sol"; import { SafeCast } from "../libraries/SafeCast.sol"; import { HyperdriveBase } from "./HyperdriveBase.sol"; import { HyperdriveLong } from "./HyperdriveLong.sol"; import { HyperdriveShort } from "./HyperdriveShort.sol"; /// @author DELV /// @notice Implements the checkpoint accounting for Hyperdrive. /// @custom:disclaimer The language used in this code is for coding convenience /// only, and is not intended to, and does not, have any /// particular legal or regulatory significance. abstract contract HyperdriveCheckpoint is IHyperdriveEvents, HyperdriveBase, HyperdriveLong, HyperdriveShort { using ExcessivelySafeCall for address; using FixedPointMath for uint256; using FixedPointMath for int256; using SafeCast for uint256; /// @dev Attempts to mint a checkpoint with the specified checkpoint time. /// @param _checkpointTime The time of the checkpoint to create. /// @param _maxIterations The number of iterations to use in the Newton's /// method component of `_distributeExcessIdleSafe`. This defaults to /// `LPMath.SHARE_PROCEEDS_MAX_ITERATIONS` if the specified value is /// smaller than the constant. function _checkpoint( uint256 _checkpointTime, uint256 _maxIterations ) internal nonReentrant { // If the checkpoint has already been set, attempt to distribute excess // idle and return early. uint256 vaultSharePrice = _pricePerVaultShare(); if (_checkpoints[_checkpointTime].vaultSharePrice != 0) { // Distribute the excess idle to the withdrawal pool. If the // distribute excess idle calculation fails, we proceed with the // calculation since checkpoints should be minted regardless of // whether idle could be distributed. _distributeExcessIdleSafe(vaultSharePrice, _maxIterations); return; } // If the checkpoint time isn't divisible by the checkpoint duration // or is in the future, it's an invalid checkpoint and we should // revert. uint256 latestCheckpoint = _latestCheckpoint(); if ( _checkpointTime % _checkpointDuration != 0 || latestCheckpoint < _checkpointTime ) { revert IHyperdrive.InvalidCheckpointTime(); } // Apply the checkpoint. _applyCheckpoint( _checkpointTime, vaultSharePrice, _maxIterations, false ); } /// @dev Creates a new checkpoint if necessary. /// @param _checkpointTime The time of the checkpoint to create. /// @param _vaultSharePrice The current vault share price. /// @param _maxIterations The number of iterations to use in the Newton's /// method component of `_distributeExcessIdleSafe`. This defaults to /// `LPMath.SHARE_PROCEEDS_MAX_ITERATIONS` if the specified value is /// smaller than the constant. /// @param _isTrader A boolean indicating whether or not the checkpoint was /// minted by a trader or by someone calling checkpoint directly. /// @return The opening vault share price of the checkpoint. function _applyCheckpoint( uint256 _checkpointTime, uint256 _vaultSharePrice, uint256 _maxIterations, bool _isTrader ) internal override returns (uint256) { // Return early if the checkpoint has already been updated. IHyperdrive.Checkpoint storage checkpoint = _checkpoints[ _checkpointTime ]; if ( checkpoint.vaultSharePrice != 0 || _checkpointTime > block.timestamp ) { return checkpoint.vaultSharePrice; } // If the checkpoint time is the latest checkpoint, we use the current // vault share price and spot price. Otherwise, we use a linear search // to find the closest non-zero vault share price and use that to // perform the checkpoint. We use the weighted spot price from the // checkpoint with the closest vault share price to populate the // weighted spot price. uint256 checkpointVaultSharePrice; uint256 checkpointWeightedSpotPrice; uint256 latestCheckpoint = _latestCheckpoint(); { uint256 nextCheckpointTime = _checkpointTime + _checkpointDuration; for (; nextCheckpointTime < latestCheckpoint; ) { // If the time isn't the latest checkpoint, we check to see if // the checkpoint's vault share price is non-zero. If it is, // that is the vault share price that we'll use to create the // new checkpoint. We'll use the corresponding weighted spot // price to instantiate the weighted spot price for the new // checkpoint. uint256 futureVaultSharePrice = _checkpoints[nextCheckpointTime] .vaultSharePrice; if (futureVaultSharePrice != 0) { checkpointVaultSharePrice = futureVaultSharePrice; checkpointWeightedSpotPrice = _checkpoints[ nextCheckpointTime ].weightedSpotPrice; break; } // Update the next checkpoint time. unchecked { nextCheckpointTime += _checkpointDuration; } } if (checkpointVaultSharePrice == 0) { checkpointVaultSharePrice = _vaultSharePrice; checkpointWeightedSpotPrice = HyperdriveMath.calculateSpotPrice( _effectiveShareReserves(), _marketState.bondReserves, _initialVaultSharePrice, _timeStretch ); } } // Create the vault share price checkpoint. checkpoint.vaultSharePrice = checkpointVaultSharePrice.toUint128(); // Update the weighted spot price for the previous checkpoint. _updateWeightedSpotPrice( _checkpointTime - _checkpointDuration, _checkpointTime, checkpointWeightedSpotPrice ); // Update the weighted spot price for the current checkpoint. _updateWeightedSpotPrice( _checkpointTime, // NOTE: We use the block time as the update time for the // latest checkpoint. For past checkpoints, we use the end time of // the checkpoint. block.timestamp.min(_checkpointTime + _checkpointDuration), checkpointWeightedSpotPrice ); // Collect the interest that has accrued since the last checkpoint. _collectZombieInterest(_vaultSharePrice); // Close out all of the short positions that matured at the beginning of // this checkpoint. This ensures that shorts don't continue to collect // free variable interest and that LP's can withdraw the proceeds of // their side of the trade. Closing out shorts first helps with netting // by ensuring the LP funds that were netted with longs are back in the // shareReserves before we close out the longs. uint256 openVaultSharePrice = _checkpoints[ _checkpointTime - _positionDuration ].vaultSharePrice; uint256 shortAssetId = AssetId.encodeAssetId( AssetId.AssetIdPrefix.Short, _checkpointTime ); uint256 maturedShortsAmount = _totalSupply[shortAssetId]; bool positionsClosed; uint256 checkpointTime = _checkpointTime; // avoid stack-too-deep uint256 vaultSharePrice = _vaultSharePrice; // avoid stack-too-deep if (maturedShortsAmount > 0) { // Since we're closing out short positions, we'll need to distribute // excess idle once the accounting updates have been performed. positionsClosed = true; // Apply the governance and LP proceeds from closing out the matured // short positions to the state. ( uint256 shareProceeds, uint256 governanceFee ) = _calculateMaturedProceeds( maturedShortsAmount, openVaultSharePrice, checkpointVaultSharePrice, vaultSharePrice, false ); _governanceFeesAccrued += governanceFee; _applyCloseShort( maturedShortsAmount, 0, shareProceeds, shareProceeds.toInt256(), // keep the effective share reserves constant checkpointTime ); // Add the governance fee back to the share proceeds. We removed it // from the LP's share proceeds since the fee is paid to governance; // however, the shorts must pay the flat fee. shareProceeds += governanceFee; // Calculate the share proceeds owed to the matured short positions. // Since the shorts have matured and the bonds have matured to a // value of 1, this is the amount of variable interest that the // shorts earned minus the flat fee. // // NOTE: Round down to underestimate the short proceeds. shareProceeds = HyperdriveMath.calculateShortProceedsDown( maturedShortsAmount, shareProceeds, openVaultSharePrice, checkpointVaultSharePrice, vaultSharePrice, _flatFee ); // Add the short proceeds to the zombie base proceeds and share // reserves. // // NOTE: Round down to underestimate the short proceeds. _marketState.zombieBaseProceeds += shareProceeds .mulDown(vaultSharePrice) .toUint112(); _marketState.zombieShareReserves += shareProceeds.toUint128(); } // Close out all of the long positions that matured at the beginning of // this checkpoint. uint256 longAssetId = AssetId.encodeAssetId( AssetId.AssetIdPrefix.Long, checkpointTime ); uint256 maturedLongsAmount = _totalSupply[longAssetId]; if (maturedLongsAmount > 0) { // Since we're closing out long positions, we'll need to distribute // excess idle once the accounting updates have been performed. positionsClosed = true; // Apply the governance and LP proceeds from closing out the matured // long positions to the state. ( uint256 shareProceeds, uint256 governanceFee ) = _calculateMaturedProceeds( maturedLongsAmount, openVaultSharePrice, checkpointVaultSharePrice, vaultSharePrice, true ); _governanceFeesAccrued += governanceFee; _applyCloseLong( maturedLongsAmount, 0, shareProceeds, shareProceeds.toInt256(), // keep the effective share reserves constant checkpointTime ); // Subtract the governance fee out when we add // share proceeds to the zombie share reserves. shareProceeds -= governanceFee; // Add the long proceeds to the zombie base proceeds and share // reserves. // // NOTE: Round down to underestimate the long proceeds. _marketState.zombieBaseProceeds += shareProceeds .mulDown(vaultSharePrice) .toUint112(); _marketState.zombieShareReserves += shareProceeds.toUint128(); } // If we closed any positions, update the global long exposure and // distribute any excess idle to the withdrawal pool. if (positionsClosed) { // Update the global long exposure. Since we've closed some matured // positions, we can reduce the long exposure for the matured // checkpoint to zero. _updateLongExposure( maturedLongsAmount.toInt256() - maturedShortsAmount.toInt256(), 0 ); // Distribute the excess idle to the withdrawal pool. If the // distribute excess idle calculation fails, we proceed with the // calculation since checkpoints should be minted regardless of // whether idle could be distributed. uint256 maxIterations = _maxIterations; // avoid stack-too-deep _distributeExcessIdleSafe(vaultSharePrice, maxIterations); } // Emit an event about the checkpoint creation that includes the LP // share price. If the LP share price calculation fails, we proceed in // minting the checkpoint and just emit the LP share price as zero. This // ensures that the system's liveness isn't impacted by temporarily // being unable to calculate the present value. (uint256 lpSharePrice, ) = _calculateLPSharePriceSafe(vaultSharePrice); emit CreateCheckpoint( checkpointTime, checkpointVaultSharePrice, vaultSharePrice, maturedShortsAmount, maturedLongsAmount, lpSharePrice ); // Claim the checkpoint reward on behalf of the sender. // // NOTE: We do this in a low-level call and ignore the status to ensure // that the checkpoint will be minted regardless of whether or not the // call succeeds. Furthermore, we use the `ExcessivelySafeCall` library // to prevent returndata bombing. bool isTrader = _isTrader; // avoid stack-too-deep address checkpointRewarder = _adminController.checkpointRewarder(); if (checkpointRewarder != address(0)) { checkpointRewarder.excessivelySafeCall( gasleft(), 0, // value of 0 0, // max copy of 0 bytes abi.encodeCall( IHyperdriveCheckpointRewarder.claimCheckpointReward, (msg.sender, checkpointTime, isTrader) ) ); } return checkpointVaultSharePrice; } /// @dev Calculates the proceeds of the holders of a given position at /// maturity. /// @param _bondAmount The bond amount of the position. /// @param _openVaultSharePrice The vault share price from the position's /// starting checkpoint. /// @param _closeVaultSharePrice The vault share price from the position's /// ending checkpoint. /// @param _vaultSharePrice The current vault share price. /// @param _isLong A flag indicating whether or not the position is a long. /// @return shareProceeds The proceeds of the holders in shares. /// @return governanceFee The fee paid to governance in shares. function _calculateMaturedProceeds( uint256 _bondAmount, uint256 _openVaultSharePrice, uint256 _closeVaultSharePrice, uint256 _vaultSharePrice, bool _isLong ) internal view returns (uint256 shareProceeds, uint256 governanceFee) { // Calculate the share proceeds, flat fee, and governance fee. Since the // position is closed at maturity, the share proceeds are equal to the // bond amount divided by the vault share price. // // NOTE: Round down to underestimate the share proceeds, flat fee, and // governance fee. shareProceeds = _bondAmount.divDown(_vaultSharePrice); uint256 flatFee = shareProceeds.mulDown(_flatFee); governanceFee = flatFee.mulDown(_governanceLPFee); // If the position is a long, the share proceeds are removed from the // share reserves. The proceeds are decreased by the flat fee because // the trader pays the flat fee. Most of the flat fee is paid to the // reserves; however, a portion of the flat fee is paid to governance. // With this in mind, we also increase the share proceeds by the // governance fee. if (_isLong) { shareProceeds -= flatFee - governanceFee; } // If the position is a short, the share proceeds are added to the share // reserves. The proceeds are increased by the flat fee because the pool // receives the flat fee. Most of the flat fee is paid to the reserves; // however, a portion of the flat fee is paid to governance. With this // in mind, we also decrease the share proceeds by the governance fee. else { shareProceeds += flatFee - governanceFee; } // If negative interest accrued over the period, the proceeds and // governance fee are given a "haircut" proportional to the negative // interest that accrued. if (_closeVaultSharePrice < _openVaultSharePrice) { // NOTE: Round down to underestimate the proceeds. shareProceeds = shareProceeds.mulDivDown( _closeVaultSharePrice, _openVaultSharePrice ); // NOTE: Round down to underestimate the governance fee. governanceFee = governanceFee.mulDivDown( _closeVaultSharePrice, _openVaultSharePrice ); } } }
// SPDX-License-Identifier: Apache-2.0 pragma solidity 0.8.22; import { IHyperdrive } from "../interfaces/IHyperdrive.sol"; import { IHyperdriveEvents } from "../interfaces/IHyperdriveEvents.sol"; import { AssetId } from "../libraries/AssetId.sol"; import { Errors } from "../libraries/Errors.sol"; import { FixedPointMath, ONE } from "../libraries/FixedPointMath.sol"; import { HyperdriveMath } from "../libraries/HyperdriveMath.sol"; import { LPMath } from "../libraries/LPMath.sol"; import { SafeCast } from "../libraries/SafeCast.sol"; import { HyperdriveLP } from "./HyperdriveLP.sol"; /// @author DELV /// @title HyperdriveLong /// @notice Implements the long accounting for Hyperdrive. /// @custom:disclaimer The language used in this code is for coding convenience /// only, and is not intended to, and does not, have any /// particular legal or regulatory significance. abstract contract HyperdriveLong is IHyperdriveEvents, HyperdriveLP { using FixedPointMath for uint256; using FixedPointMath for int256; using SafeCast for uint256; using SafeCast for int256; /// @dev Opens a long position. /// @param _amount The amount of capital provided to open the long. The /// units of this quantity are either base or vault shares, depending /// on the value of `_options.asBase`. /// @param _minOutput The minimum number of bonds to receive. /// @param _minVaultSharePrice The minimum vault share price at which to /// open the long. This allows traders to protect themselves from /// opening a long in a checkpoint where negative interest has /// accrued. /// @param _options The options that configure how the trade is settled. /// @return maturityTime The maturity time of the bonds. /// @return bondProceeds The amount of bonds the user received. function _openLong( uint256 _amount, uint256 _minOutput, uint256 _minVaultSharePrice, IHyperdrive.Options calldata _options ) internal nonReentrant isNotPaused returns (uint256 maturityTime, uint256 bondProceeds) { // Check that the message value is valid. _checkMessageValue(); // Check that the provided options are valid. _checkOptions(_options); // Deposit the user's input amount. (uint256 sharesDeposited, uint256 vaultSharePrice) = _deposit( _amount, _options ); // Enforce the minimum user outputs and the minimum vault share price. // // NOTE: We use the value that is returned from the deposit to check // against the minimum transaction amount because in the event of // slippage on the deposit, we want the inputs to the state updates to // respect the minimum transaction amount requirements. // // NOTE: Round down to underestimate the base deposit. This makes the // minimum transaction amount check more conservative. uint256 baseDeposited = sharesDeposited.mulDown(vaultSharePrice); if (baseDeposited < _minimumTransactionAmount) { revert IHyperdrive.MinimumTransactionAmount(); } if (vaultSharePrice < _minVaultSharePrice) { revert IHyperdrive.MinimumSharePrice(); } // Perform a checkpoint. uint256 latestCheckpoint = _latestCheckpoint(); _applyCheckpoint( latestCheckpoint, vaultSharePrice, LPMath.SHARE_PROCEEDS_MAX_ITERATIONS, true ); // Calculate the pool and user deltas using the trading function. We // backdate the bonds purchased to the beginning of the checkpoint. // Note: All state deltas are derived from the output of the // deposit function. uint256 shareReservesDelta; uint256 totalGovernanceFee; uint256 spotPrice; ( shareReservesDelta, bondProceeds, totalGovernanceFee, spotPrice ) = _calculateOpenLong(sharesDeposited, vaultSharePrice); // Enforce the minimum user outputs. if (bondProceeds < _minOutput) { revert IHyperdrive.OutputLimit(); } // Attribute the governance fee. _governanceFeesAccrued += totalGovernanceFee; // Update the weighted spot price. _updateWeightedSpotPrice(latestCheckpoint, block.timestamp, spotPrice); // Apply the open long to the state. maturityTime = latestCheckpoint + _positionDuration; _applyOpenLong( shareReservesDelta, bondProceeds, vaultSharePrice, maturityTime ); // Mint the bonds to the trader with an ID of the maturity time. uint256 assetId = AssetId.encodeAssetId( AssetId.AssetIdPrefix.Long, maturityTime ); _mint(assetId, _options.destination, bondProceeds); // Emit an OpenLong event. uint256 amount = _amount; // Avoid stack too deep error. uint256 maturityTime_ = maturityTime; // Avoid stack too deep error. uint256 bondProceeds_ = bondProceeds; // Avoid stack too deep error. uint256 vaultSharePrice_ = vaultSharePrice; // Avoid stack too deep error. IHyperdrive.Options calldata options = _options; // Avoid stack too deep error. emit OpenLong( options.destination, assetId, maturityTime_, amount, vaultSharePrice_, options.asBase, bondProceeds_, options.extraData ); return (maturityTime, bondProceeds_); } /// @dev Closes a long position with a specified maturity time. /// @param _maturityTime The maturity time of the long. /// @param _bondAmount The amount of longs to close. /// @param _minOutput The minimum proceeds the trader will accept. The units /// of this quantity are either base or vault shares, depending on /// the value of `_options.asBase`. /// @param _options The options that configure how the trade is settled. /// @return The proceeds the user receives. The units of this quantity are /// either base or vault shares, depending on the value of /// `_options.asBase`. function _closeLong( uint256 _maturityTime, uint256 _bondAmount, uint256 _minOutput, IHyperdrive.Options calldata _options ) internal nonReentrant returns (uint256) { // Check that the provided options are valid. _checkOptions(_options); // Ensure that the bond amount is greater than or equal to the minimum // transaction amount. if (_bondAmount < _minimumTransactionAmount) { revert IHyperdrive.MinimumTransactionAmount(); } // If the long hasn't matured, we checkpoint the latest checkpoint. // Otherwise, we perform a checkpoint at the time the long matured. // This ensures the long and all of the other positions in the // checkpoint are closed. uint256 vaultSharePrice = _pricePerVaultShare(); if (block.timestamp < _maturityTime) { _applyCheckpoint( _latestCheckpoint(), vaultSharePrice, LPMath.SHARE_PROCEEDS_MAX_ITERATIONS, true ); } else { _applyCheckpoint( _maturityTime, vaultSharePrice, LPMath.SHARE_PROCEEDS_MAX_ITERATIONS, true ); } // Burn the longs that are being closed. _burn( AssetId.encodeAssetId(AssetId.AssetIdPrefix.Long, _maturityTime), msg.sender, _bondAmount ); // Calculate the pool and user deltas using the trading function. // Note: All state deltas are derived from external function inputs. ( uint256 bondReservesDelta, uint256 shareProceeds, uint256 shareReservesDelta, int256 shareAdjustmentDelta, uint256 totalGovernanceFee, uint256 spotPrice ) = _calculateCloseLong(_bondAmount, vaultSharePrice, _maturityTime); // If the position hasn't matured, apply the accounting updates that // result from closing the long to the reserves and pay out the // withdrawal pool if necessary. uint256 maturityTime = _maturityTime; // Avoid stack too deep error. if (block.timestamp < _maturityTime) { // Attribute the governance fee. _governanceFeesAccrued += totalGovernanceFee; // Update the weighted spot price. _updateWeightedSpotPrice( _latestCheckpoint(), block.timestamp, spotPrice ); // Apply the close long to the state. _applyCloseLong( _bondAmount, bondReservesDelta, shareReservesDelta, shareAdjustmentDelta, maturityTime ); // Update the global long exposure. Since we're closing a long, the // number of non-netted longs decreases by the bond amount. int256 nonNettedLongs = _nonNettedLongs(maturityTime); _updateLongExposure( nonNettedLongs + _bondAmount.toInt256(), nonNettedLongs ); // Closing longs decreases the share reserves. When the longs that // are being closed are partially or fully netted out, it's possible // that fully closing the long could make the system insolvent. if (!_isSolvent(vaultSharePrice)) { Errors.throwInsufficientLiquidityError(); } // Distribute the excess idle to the withdrawal pool. If the // distribute excess idle calculation fails, we revert to avoid // putting the system in an unhealthy state after the trade is // processed. bool success = _distributeExcessIdleSafe(vaultSharePrice); if (!success) { revert IHyperdrive.DistributeExcessIdleFailed(); } } else { // Apply the zombie close to the state and adjust the share proceeds // to account for negative interest that might have accrued to the // zombie share reserves. shareProceeds = _applyZombieClose(shareProceeds, vaultSharePrice); // Distribute the excess idle to the withdrawal pool. If the // distribute excess idle calculation fails, we proceed with the // calculation since traders should be able to close their positions // at maturity regardless of whether idle could be distributed. _distributeExcessIdleSafe(vaultSharePrice); } // Withdraw the profit to the trader. uint256 proceeds = _withdraw(shareProceeds, vaultSharePrice, _options); // Enforce the minimum user outputs. // // NOTE: We use the value that is returned from the withdraw to check // against the minOutput because in the event of slippage on the // withdraw, we want it to be caught be the minOutput check. if (proceeds < _minOutput) { revert IHyperdrive.OutputLimit(); } // Emit a CloseLong event. uint256 bondAmount = _bondAmount; // Avoid stack too deep error. uint256 vaultSharePrice_ = vaultSharePrice; // Avoid stack too deep error. IHyperdrive.Options calldata options = _options; // Avoid stack too deep error. emit CloseLong( msg.sender, // trader options.destination, // destination AssetId.encodeAssetId(AssetId.AssetIdPrefix.Long, maturityTime), maturityTime, proceeds, vaultSharePrice_, options.asBase, bondAmount, options.extraData ); return proceeds; } /// @dev Applies an open long to the state. This includes updating the /// reserves and maintaining the reserve invariants. /// @param _shareReservesDelta The amount of shares paid to the curve. /// @param _bondReservesDelta The amount of bonds sold by the curve. /// @param _vaultSharePrice The current vault share price. /// @param _maturityTime The maturity time of the long. function _applyOpenLong( uint256 _shareReservesDelta, uint256 _bondReservesDelta, uint256 _vaultSharePrice, uint256 _maturityTime ) internal { // Update the average maturity time of long positions. uint128 longsOutstanding_ = _marketState.longsOutstanding; _marketState.longAverageMaturityTime = uint256( _marketState.longAverageMaturityTime ) .updateWeightedAverage( longsOutstanding_, _maturityTime * ONE, // scale up to fixed point scale _bondReservesDelta, true ) .toUint128(); // Apply the trading deltas to the reserves and update the amount of // longs outstanding. _marketState.shareReserves += _shareReservesDelta.toUint128(); _marketState.bondReserves -= _bondReservesDelta.toUint128(); longsOutstanding_ += _bondReservesDelta.toUint128(); _marketState.longsOutstanding = longsOutstanding_; // Update the global long exposure. Since we're opening a long, the // number of non-netted longs increases by the bond amount. int256 nonNettedLongs = _nonNettedLongs(_maturityTime); _updateLongExposure( nonNettedLongs, nonNettedLongs + _bondReservesDelta.toInt256() ); // We need to check solvency because longs increase the system's exposure. if (!_isSolvent(_vaultSharePrice)) { Errors.throwInsufficientLiquidityError(); } // Distribute the excess idle to the withdrawal pool. If the distribute // excess idle calculation fails, we revert to avoid putting the system // in an unhealthy state after the trade is processed. bool success = _distributeExcessIdleSafe(_vaultSharePrice); if (!success) { revert IHyperdrive.DistributeExcessIdleFailed(); } } /// @dev Applies the trading deltas from a closed long to the reserves and /// the withdrawal pool. /// @param _bondAmount The amount of longs that were closed. /// @param _bondReservesDelta The bonds to add to the reserves. /// @param _shareReservesDelta The shares to remove from the reserves. /// @param _shareAdjustmentDelta The amount to decrease the share adjustment. /// @param _maturityTime The maturity time of the long. function _applyCloseLong( uint256 _bondAmount, uint256 _bondReservesDelta, uint256 _shareReservesDelta, int256 _shareAdjustmentDelta, uint256 _maturityTime ) internal { // The share reserves are decreased in this operation, so we need to // verify the invariant that z >= z_min is satisfied. uint256 shareReserves = _marketState.shareReserves; if ( shareReserves < _shareReservesDelta || shareReserves - _shareReservesDelta < _minimumShareReserves ) { Errors.throwInsufficientLiquidityError(); } unchecked { shareReserves -= _shareReservesDelta; } // If the effective share reserves are decreasing, then we need to // verify that z - zeta >= z_min is satisfied. // // NOTE: Avoiding this check when the effective share reserves aren't // decreasing is important since `removeLiquidity` can result in an // effective share reserves less than the minimum share reserves, and // it's important that this doesn't result in failed checkpoints. int256 shareAdjustment = _marketState.shareAdjustment; shareAdjustment -= _shareAdjustmentDelta; if ( _shareReservesDelta.toInt256() > _shareAdjustmentDelta && HyperdriveMath.calculateEffectiveShareReserves( shareReserves, shareAdjustment ) < _minimumShareReserves ) { Errors.throwInsufficientLiquidityError(); } // Update the long average maturity time. uint256 longsOutstanding = _marketState.longsOutstanding; _marketState.longAverageMaturityTime = uint256( _marketState.longAverageMaturityTime ) .updateWeightedAverage( longsOutstanding, _maturityTime * ONE, // scale up to fixed point scale _bondAmount, false ) .toUint128(); // Reduce the amount of outstanding longs. longsOutstanding -= _bondAmount; _marketState.longsOutstanding = longsOutstanding.toUint128(); // Apply the updates from the curve and flat components of the trade to // the reserves. The share proceeds are added to the share reserves // since the LPs are buying bonds for shares. The bond reserves are // increased by the curve component to decrease the spot price. The // share adjustment is increased by the flat component of the share // reserves update so that we can translate the curve to hold the // pricing invariant under the flat update. _marketState.shareReserves = shareReserves.toUint128(); _marketState.shareAdjustment = shareAdjustment.toInt128(); _marketState.bondReserves += _bondReservesDelta.toUint128(); } /// @dev Calculate the pool reserve and trader deltas that result from /// opening a long. This calculation includes trading fees. /// @param _shareAmount The amount of shares being paid to open the long. /// @param _vaultSharePrice The current vault share price. /// @return shareReservesDelta The change in the share reserves. /// @return bondReservesDelta The change in the bond reserves. /// @return totalGovernanceFee The governance fee in shares. /// @return spotPrice The pool's current spot price. function _calculateOpenLong( uint256 _shareAmount, uint256 _vaultSharePrice ) internal view returns ( uint256 shareReservesDelta, uint256 bondReservesDelta, uint256 totalGovernanceFee, uint256 spotPrice ) { // Calculate the effect that opening the long should have on the pool's // reserves as well as the amount of bond the trader receives. uint256 effectiveShareReserves = _effectiveShareReserves(); bondReservesDelta = HyperdriveMath.calculateOpenLong( effectiveShareReserves, _marketState.bondReserves, _shareAmount, // amountIn _timeStretch, _vaultSharePrice, _initialVaultSharePrice ); // Ensure that the trader didn't purchase bonds at a negative interest // rate after accounting for fees. spotPrice = HyperdriveMath.calculateSpotPrice( effectiveShareReserves, _marketState.bondReserves, _initialVaultSharePrice, _timeStretch ); if ( _isNegativeInterest( _shareAmount, bondReservesDelta, HyperdriveMath.calculateOpenLongMaxSpotPrice( spotPrice, _curveFee, _flatFee ) ) ) { Errors.throwInsufficientLiquidityError(); } // Calculate the fees paid to open the long and apply these fees to the // reserves deltas. ( shareReservesDelta, bondReservesDelta, totalGovernanceFee ) = _calculateOpenLongFees( _shareAmount, bondReservesDelta, _vaultSharePrice, spotPrice ); // Ensure that the ending spot price is less than or equal to one. // Despite the fact that the earlier negative interest check should // imply this, we perform this check out of an abundance of caution // since the `pow` function is known to not be monotonic. if ( HyperdriveMath.calculateSpotPrice( effectiveShareReserves + shareReservesDelta, _marketState.bondReserves - bondReservesDelta, _initialVaultSharePrice, _timeStretch ) > ONE ) { Errors.throwInsufficientLiquidityError(); } return ( shareReservesDelta, bondReservesDelta, totalGovernanceFee, spotPrice ); } /// @dev Calculate the pool reserve and trader deltas that result from /// closing a long. This calculation includes trading fees. /// @param _bondAmount The amount of bonds being purchased to close the short. /// @param _vaultSharePrice The current vault share price. /// @param _maturityTime The maturity time of the short position. /// @return bondReservesDelta The bonds added to the reserves. /// @return shareProceeds The proceeds in shares of selling the bonds. /// @return shareReservesDelta The shares removed from the reserves. /// @return shareAdjustmentDelta The change in the share adjustment. /// @return totalGovernanceFee The governance fee in shares. function _calculateCloseLong( uint256 _bondAmount, uint256 _vaultSharePrice, uint256 _maturityTime ) internal view returns ( uint256 bondReservesDelta, uint256 shareProceeds, uint256 shareReservesDelta, int256 shareAdjustmentDelta, uint256 totalGovernanceFee, uint256 spotPrice ) { // Calculate the effect that closing the long should have on the pool's // reserves as well as the amount of shares the trader receives for // selling their bonds. uint256 shareCurveDelta; { // Calculate the effect that closing the long should have on the // pool's reserves as well as the amount of shares the trader // receives for selling the bonds at the market price. // // NOTE: We calculate the time remaining from the latest checkpoint // to ensure that opening/closing a position doesn't result in // immediate profit. uint256 effectiveShareReserves = _effectiveShareReserves(); uint256 timeRemaining = _calculateTimeRemaining(_maturityTime); uint256 vaultSharePrice = _vaultSharePrice; // avoid stack-too-deep uint256 bondAmount = _bondAmount; // avoid stack-too-deep (shareCurveDelta, bondReservesDelta, shareProceeds) = HyperdriveMath .calculateCloseLong( effectiveShareReserves, _marketState.bondReserves, bondAmount, timeRemaining, _timeStretch, vaultSharePrice, _initialVaultSharePrice ); // Calculate the fees that should be paid by the trader. The trader // pays a fee on the curve and flat parts of the trade. Most of the // fees go the LPs, but a portion goes to governance. uint256 curveFee; uint256 governanceCurveFee; uint256 flatFee; spotPrice = HyperdriveMath.calculateSpotPrice( effectiveShareReserves, _marketState.bondReserves, _initialVaultSharePrice, _timeStretch ); ( curveFee, // shares flatFee, // shares governanceCurveFee, // shares totalGovernanceFee // shares ) = _calculateFeesGivenBonds( bondAmount, timeRemaining, spotPrice, vaultSharePrice ); // The curve fee (shares) is paid to the LPs, so we subtract it from // the share curve delta (shares) to prevent it from being debited // from the reserves when the state is updated. The governance curve // fee (shares) is paid to governance, so we add it back to the // share curve delta (shares) to ensure that the governance fee // isn't included in the share adjustment. shareCurveDelta -= (curveFee - governanceCurveFee); // The trader pays the curve fee (shares) and flat fee (shares) to // the pool, so we debit them from the trader's share proceeds // (shares). shareProceeds -= curveFee + flatFee; // We applied the full curve and flat fees to the share proceeds, // which reduce the trader's proceeds. To calculate the payment that // is applied to the share reserves (and is effectively paid by the // LPs), we need to add governance's portion of these fees to the // share proceeds. shareReservesDelta = shareProceeds + totalGovernanceFee; } // Adjust the computed proceeds and delta for negative interest. // We also compute the share adjustment delta at this step to ensure // that we don't break our AMM invariant when we account for negative // interest and flat adjustments. ( shareProceeds, shareReservesDelta, shareCurveDelta, shareAdjustmentDelta, totalGovernanceFee ) = HyperdriveMath.calculateNegativeInterestOnClose( shareProceeds, shareReservesDelta, shareCurveDelta, totalGovernanceFee, // NOTE: We use the vault share price from the beginning of the // checkpoint as the open vault share price. This means that a // trader that opens a long in a checkpoint that has negative // interest accrued will be penalized for the negative interest when // they try to close their position. The `_minVaultSharePrice` // parameter allows traders to protect themselves from this edge // case. _checkpoints[_maturityTime - _positionDuration].vaultSharePrice, // open vault share price block.timestamp < _maturityTime ? _vaultSharePrice : _checkpoints[_maturityTime].vaultSharePrice, // close vault share price true ); return ( bondReservesDelta, shareProceeds, shareReservesDelta, shareAdjustmentDelta, totalGovernanceFee, spotPrice ); } }
// SPDX-License-Identifier: Apache-2.0 pragma solidity 0.8.22; import { IHyperdrive } from "../interfaces/IHyperdrive.sol"; import { IHyperdriveEvents } from "../interfaces/IHyperdriveEvents.sol"; import { AssetId } from "../libraries/AssetId.sol"; import { FixedPointMath } from "../libraries/FixedPointMath.sol"; import { HyperdriveMath } from "../libraries/HyperdriveMath.sol"; import { LPMath } from "../libraries/LPMath.sol"; import { SafeCast } from "../libraries/SafeCast.sol"; import { HyperdriveBase } from "./HyperdriveBase.sol"; import { HyperdriveMultiToken } from "./HyperdriveMultiToken.sol"; /// @author DELV /// @title HyperdriveLP /// @notice Implements the LP accounting for Hyperdrive. /// @custom:disclaimer The language used in this code is for coding convenience /// only, and is not intended to, and does not, have any /// particular legal or regulatory significance. abstract contract HyperdriveLP is IHyperdriveEvents, HyperdriveBase, HyperdriveMultiToken { using FixedPointMath for uint256; using FixedPointMath for int256; using LPMath for LPMath.PresentValueParams; using SafeCast for int256; using SafeCast for uint256; /// @dev Allows the first LP to initialize the market with a target APR. /// @param _contribution The amount of capital to supply. The units of this /// quantity are either base or vault shares, depending on the value /// of `_options.asBase`. /// @param _apr The target APR. /// @param _options The options that configure how the operation is settled. /// @return lpShares The initial number of LP shares created. function _initialize( uint256 _contribution, uint256 _apr, IHyperdrive.Options calldata _options ) internal nonReentrant returns (uint256 lpShares) { // Check that the message value and base amount are valid. _checkMessageValue(); // Check that the provided options are valid. _checkOptions(_options); // Ensure that the pool hasn't been initialized yet. if (_marketState.isInitialized) { revert IHyperdrive.PoolAlreadyInitialized(); } // Deposit the users contribution and get the amount of shares that // their contribution was worth. (uint256 shareContribution, uint256 vaultSharePrice) = _deposit( _contribution, _options ); // Ensure that the contribution is large enough to set aside the minimum // share reserves permanently. After initialization, none of the LPs // will have a claim on the minimum share reserves, and longs and shorts // will not be able to consume this liquidity. This ensures that the // share reserves are always greater than zero, which prevents a host of // numerical issues when we are updating the reserves during normal // operations. As an additional precaution, we will also set aside an // amount of shares equaling the minimum share reserves as the initial // LP contribution from the zero address. This ensures that the total // LP supply will always be greater than or equal to the minimum share // reserves, which is helping for preventing donation attacks and other // numerical issues. if (shareContribution < 2 * _minimumShareReserves) { revert IHyperdrive.BelowMinimumContribution(); } unchecked { lpShares = shareContribution - 2 * _minimumShareReserves; } // Set the initialized state to true. _marketState.isInitialized = true; // Calculate the initial reserves. We ensure that the effective share // reserves is larger than the minimum share reserves. This ensures that // round-trip properties hold after the pool is initialized. ( uint256 shareReserves, int256 shareAdjustment, uint256 bondReserves ) = LPMath.calculateInitialReserves( shareContribution, vaultSharePrice, _initialVaultSharePrice, _apr, _positionDuration, _timeStretch ); if ( HyperdriveMath.calculateEffectiveShareReserves( shareReserves, shareAdjustment ) < _minimumShareReserves ) { revert IHyperdrive.InvalidEffectiveShareReserves(); } // Check to see whether or not the initial liquidity will result in // invalid price discovery. If the spot price can't be brought to one, // we revert to avoid dangerous pool states. ( int256 solvencyAfterMaxLong, bool success ) = _calculateSolvencyAfterMaxLongSafe( shareReserves, shareAdjustment, bondReserves, vaultSharePrice, 0, 0 ); if (!success || solvencyAfterMaxLong < 0) { revert IHyperdrive.CircuitBreakerTriggered(); } // Initialize the reserves. _marketState.shareReserves = shareReserves.toUint128(); _marketState.shareAdjustment = shareAdjustment.toInt128(); _marketState.bondReserves = bondReserves.toUint128(); // Mint the minimum share reserves to the zero address as a buffer that // ensures that the total LP supply is always greater than or equal to // the minimum share reserves. The initializer will receive slightly // less shares than they contributed to cover the shares set aside as a // buffer on the share reserves and the shares set aside for the zero // address, but this is a small price to pay for the added security // in practice. _mint(AssetId._LP_ASSET_ID, address(0), _minimumShareReserves); _mint(AssetId._LP_ASSET_ID, _options.destination, lpShares); // Create an initial checkpoint. _applyCheckpoint( _latestCheckpoint(), vaultSharePrice, LPMath.SHARE_PROCEEDS_MAX_ITERATIONS, true ); // Emit an Initialize event. uint256 contribution = _contribution; // avoid stack-too-deep uint256 apr = _apr; // avoid stack-too-deep IHyperdrive.Options calldata options = _options; // avoid stack-too-deep emit Initialize( options.destination, lpShares, contribution, vaultSharePrice, options.asBase, apr, options.extraData ); return lpShares; } /// @dev Allows LPs to supply liquidity for LP shares. /// @param _contribution The amount of capital to supply. The units of this /// quantity are either base or vault shares, depending on the value /// of `_options.asBase`. /// @param _minLpSharePrice The minimum LP share price the LP is willing /// to accept for their shares. LPs incur negative slippage when /// adding liquidity if there is a net curve position in the market, /// so this allows LPs to protect themselves from high levels of /// slippage. The units of this quantity are either base or vault /// shares, depending on the value of `_options.asBase`. /// @param _minApr The minimum APR at which the LP is willing to supply. /// @param _maxApr The maximum APR at which the LP is willing to supply. /// @param _options The options that configure how the operation is settled. /// @return lpShares The number of LP tokens created. function _addLiquidity( uint256 _contribution, uint256 _minLpSharePrice, uint256 _minApr, uint256 _maxApr, IHyperdrive.Options calldata _options ) internal nonReentrant isNotPaused returns (uint256 lpShares) { // Check that the message value is valid. _checkMessageValue(); // Check that the provided options are valid. _checkOptions(_options); // Ensure that the contribution is greater than or equal to the minimum // transaction amount. if (_contribution < _minimumTransactionAmount) { revert IHyperdrive.MinimumTransactionAmount(); } // Enforce the slippage guard. uint256 apr = HyperdriveMath.calculateSpotAPR( _effectiveShareReserves(), _marketState.bondReserves, _initialVaultSharePrice, _positionDuration, _timeStretch ); if (apr < _minApr || apr > _maxApr) { revert IHyperdrive.InvalidApr(); } // Deposit for the user, this call also transfers from them (uint256 shareContribution, uint256 vaultSharePrice) = _deposit( _contribution, _options ); // Perform a checkpoint. uint256 latestCheckpoint = _latestCheckpoint(); _applyCheckpoint( latestCheckpoint, vaultSharePrice, LPMath.SHARE_PROCEEDS_MAX_ITERATIONS, true ); // Calculate the solvency after opening a max long before applying the // add liquidity updates. This is a benchmark for the pool's current // price discovery. Adding liquidity should not negatively impact price // discovery. ( int256 solvencyAfterMaxLongBefore, bool success ) = _calculateSolvencyAfterMaxLongSafe( _marketState.shareReserves, _marketState.shareAdjustment, _marketState.bondReserves, vaultSharePrice, _marketState.longExposure, _nonNettedLongs(latestCheckpoint + _positionDuration) ); if (!success) { revert IHyperdrive.CircuitBreakerTriggered(); } // Ensure that the spot APR is close enough to the previous weighted // spot price to fall within the tolerance. uint256 contribution = _contribution; // avoid stack-too-deep { uint256 previousWeightedSpotAPR = HyperdriveMath .calculateAPRFromPrice( _checkpoints[latestCheckpoint - _checkpointDuration] .weightedSpotPrice, _positionDuration ); if ( apr > previousWeightedSpotAPR + _circuitBreakerDelta || (previousWeightedSpotAPR > _circuitBreakerDelta && apr < previousWeightedSpotAPR - _circuitBreakerDelta) ) { revert IHyperdrive.CircuitBreakerTriggered(); } } // Get the initial value for the total LP supply and the total supply // of withdrawal shares before the liquidity is added. The total LP // supply is given by `l = l_a + l_w - l_r` where `l_a` is the total // supply of active LP shares, `l_w` is the total supply of withdrawal // shares, and `l_r` is the amount of withdrawal shares ready for // withdrawal. uint256 withdrawalSharesOutstanding = _totalSupply[ AssetId._WITHDRAWAL_SHARE_ASSET_ID ] - _withdrawPool.readyToWithdraw; uint256 lpTotalSupply = _totalSupply[AssetId._LP_ASSET_ID] + withdrawalSharesOutstanding; // Calculate the number of LP shares to mint. uint256 endingPresentValue; uint256 startingPresentValue; { // Calculate the present value before updating the reserves. LPMath.PresentValueParams memory params = _getPresentValueParams( vaultSharePrice ); startingPresentValue = LPMath.calculatePresentValue(params); // Add the liquidity to the pool's reserves and calculate the new // present value. _updateLiquidity(shareContribution.toInt256()); params.shareReserves = _marketState.shareReserves; params.shareAdjustment = _marketState.shareAdjustment; params.bondReserves = _marketState.bondReserves; endingPresentValue = LPMath.calculatePresentValue(params); // Revert if the present value decreased after adding liquidity. if (endingPresentValue < startingPresentValue) { revert IHyperdrive.DecreasedPresentValueWhenAddingLiquidity(); } // NOTE: Round down to underestimate the amount of LP shares minted. // // The LP shares minted to the LP is derived by solving for the // change in LP shares that preserves the ratio of present value to // total LP shares. This ensures that LPs are fairly rewarded for // adding liquidity. This is given by: // // PV0 / l0 = PV1 / (l0 + dl) => dl = ((PV1 - PV0) * l0) / PV0 lpShares = (endingPresentValue - startingPresentValue).mulDivDown( lpTotalSupply, startingPresentValue ); // Ensure that enough lp shares are minted so that they can be redeemed. if (lpShares < _minimumTransactionAmount) { revert IHyperdrive.MinimumTransactionAmount(); } } // NOTE: Round down to make the check more conservative. // // Enforce the minimum LP share price slippage guard. if (contribution.divDown(lpShares) < _minLpSharePrice) { revert IHyperdrive.OutputLimit(); } // Mint LP shares to the supplier. _mint(AssetId._LP_ASSET_ID, _options.destination, lpShares); // Distribute the excess idle to the withdrawal pool. If the distribute // excess idle calculation fails, we revert to avoid allowing the system // to enter an unhealthy state. A failure indicates that the present // value can't be calculated. success = _distributeExcessIdleSafe(vaultSharePrice); if (!success) { revert IHyperdrive.DistributeExcessIdleFailed(); } // Check to see whether or not adding this liquidity will result in // worsened price discovery. If the spot price can't be brought to one // and price discovery worsened after adding liquidity, we revert to // avoid dangerous pool states. uint256 latestCheckpoint_ = latestCheckpoint; // avoid stack-too-deep uint256 lpShares_ = lpShares; // avoid stack-too-deep IHyperdrive.Options calldata options = _options; // avoid stack-too-deep uint256 vaultSharePrice_ = vaultSharePrice; // avoid stack-too-deep int256 solvencyAfterMaxLongAfter; ( solvencyAfterMaxLongAfter, success ) = _calculateSolvencyAfterMaxLongSafe( _marketState.shareReserves, _marketState.shareAdjustment, _marketState.bondReserves, vaultSharePrice_, _marketState.longExposure, _nonNettedLongs(latestCheckpoint_ + _positionDuration) ); if ( !success || solvencyAfterMaxLongAfter < solvencyAfterMaxLongBefore.min(0) ) { revert IHyperdrive.CircuitBreakerTriggered(); } // Emit an AddLiquidity event. uint256 lpSharePrice = lpTotalSupply == 0 ? 0 // NOTE: We always round the LP share price down for consistency. : startingPresentValue.mulDivDown(vaultSharePrice_, lpTotalSupply); emit AddLiquidity( options.destination, lpShares_, contribution, vaultSharePrice_, options.asBase, lpSharePrice, options.extraData ); } /// @dev Allows an LP to burn shares and withdraw from the pool. /// @param _lpShares The LP shares to burn. /// @param _minOutputPerShare The minimum amount the LP expects to receive /// for each withdrawal share that is burned. The units of this /// quantity are either base or vault shares, depending on the value /// of `_options.asBase`. /// @param _options The options that configure how the operation is settled. /// @return proceeds The amount the LP removing liquidity receives. The /// units of this quantity are either base or vault shares, depending /// on the value of `_options.asBase`. /// @return withdrawalShares The base that the LP receives buys out some of /// their LP shares, but it may not be sufficient to fully buy the /// LP out. In this case, the LP receives withdrawal shares equal /// in value to the present value they are owed. As idle capital /// becomes available, the pool will buy back these shares. function _removeLiquidity( uint256 _lpShares, uint256 _minOutputPerShare, IHyperdrive.Options calldata _options ) internal nonReentrant returns (uint256 proceeds, uint256 withdrawalShares) { // Check that the provided options are valid. _checkOptions(_options); // Ensure that the amount of LP shares to remove is greater than or // equal to the minimum transaction amount. if (_lpShares < _minimumTransactionAmount) { revert IHyperdrive.MinimumTransactionAmount(); } // Perform a checkpoint. uint256 vaultSharePrice = _pricePerVaultShare(); _applyCheckpoint( _latestCheckpoint(), vaultSharePrice, LPMath.SHARE_PROCEEDS_MAX_ITERATIONS, true ); // Burn the LP's shares. _burn(AssetId._LP_ASSET_ID, msg.sender, _lpShares); // Mint an equivalent amount of withdrawal shares. _mint( AssetId._WITHDRAWAL_SHARE_ASSET_ID, _options.destination, _lpShares ); // Redeem as many of the withdrawal shares as possible. uint256 withdrawalSharesRedeemed; (proceeds, withdrawalSharesRedeemed) = _redeemWithdrawalSharesInternal( _options.destination, _lpShares, vaultSharePrice, _minOutputPerShare, _options ); withdrawalShares = _lpShares - withdrawalSharesRedeemed; // Emit a RemoveLiquidity event. If the LP share price calculation // fails, we proceed in removing liquidity and just emit the LP share // price as zero. This ensures that the system's liveness isn't impacted // by temporarily being unable to calculate the present value. (uint256 lpSharePrice, ) = _calculateLPSharePriceSafe(vaultSharePrice); emit RemoveLiquidity( msg.sender, // provider _options.destination, // destination _lpShares, proceeds, vaultSharePrice, _options.asBase, uint256(withdrawalShares), lpSharePrice, _options.extraData ); return (proceeds, withdrawalShares); } /// @dev Redeems withdrawal shares by giving the LP a pro-rata amount of the /// withdrawal pool's proceeds. This function redeems the maximum /// amount of the specified withdrawal shares given the amount of /// withdrawal shares ready to withdraw. /// @param _withdrawalShares The withdrawal shares to redeem. /// @param _minOutputPerShare The minimum amount the LP expects to /// receive for each withdrawal share that is burned. The units of /// this quantity are either base or vault shares, depending on the /// value of `_options.asBase`. /// @param _options The options that configure how the operation is settled. /// @return proceeds The amount the LP received. The units of this quantity /// are either base or vault shares, depending on the value of /// `_options.asBase`. /// @return withdrawalSharesRedeemed The amount of withdrawal shares that /// were redeemed. function _redeemWithdrawalShares( uint256 _withdrawalShares, uint256 _minOutputPerShare, IHyperdrive.Options calldata _options ) internal nonReentrant returns (uint256 proceeds, uint256 withdrawalSharesRedeemed) { // Check that the provided options are valid. _checkOptions(_options); // Perform a checkpoint. uint256 vaultSharePrice = _pricePerVaultShare(); _applyCheckpoint( _latestCheckpoint(), vaultSharePrice, LPMath.SHARE_PROCEEDS_MAX_ITERATIONS, true ); // Redeem as many of the withdrawal shares as possible. (proceeds, withdrawalSharesRedeemed) = _redeemWithdrawalSharesInternal( msg.sender, _withdrawalShares, vaultSharePrice, _minOutputPerShare, _options ); // Emit a RedeemWithdrawalShares event. emit RedeemWithdrawalShares( msg.sender, // provider _options.destination, // destination withdrawalSharesRedeemed, proceeds, vaultSharePrice, _options.asBase, _options.extraData ); return (proceeds, withdrawalSharesRedeemed); } /// @dev Redeems withdrawal shares by giving the LP a pro-rata amount of the /// withdrawal pool's proceeds. This function redeems the maximum /// amount of the specified withdrawal shares given the amount of /// withdrawal shares ready to withdraw. /// @param _source The address that owns the withdrawal shares to redeem. /// @param _withdrawalShares The withdrawal shares to redeem. /// @param _vaultSharePrice The vault share price. /// @param _minOutputPerShare The minimum amount the LP expects to /// receive for each withdrawal share that is burned. The units of /// this quantity are either base or vault shares, depending on the /// value of `_options.asBase`. /// @param _options The options that configure how the operation is settled. /// @return proceeds The amount the LP received. The units of this quantity /// are either base or vault shares, depending on the value of /// `_options.asBase`. /// @return withdrawalSharesRedeemed The amount of withdrawal shares that /// were redeemed. function _redeemWithdrawalSharesInternal( address _source, uint256 _withdrawalShares, uint256 _vaultSharePrice, uint256 _minOutputPerShare, IHyperdrive.Options calldata _options ) internal returns (uint256 proceeds, uint256 withdrawalSharesRedeemed) { // Distribute the excess idle to the withdrawal pool. If the distribute // excess idle calculation fails, we proceed with the calculation since // LPs should be able to redeem their withdrawal shares for existing // withdrawal proceeds regardless of whether or not idle could be // distributed. _distributeExcessIdleSafe(_vaultSharePrice); // Clamp the shares to the total amount of shares ready for withdrawal // to avoid unnecessary reverts. We exit early if the user has no shares // available to redeem. withdrawalSharesRedeemed = _withdrawalShares; uint128 readyToWithdraw_ = _withdrawPool.readyToWithdraw; if (withdrawalSharesRedeemed > readyToWithdraw_) { withdrawalSharesRedeemed = readyToWithdraw_; } if (withdrawalSharesRedeemed == 0) return (0, 0); // We burn the shares from the user. _burn( AssetId._WITHDRAWAL_SHARE_ASSET_ID, _source, withdrawalSharesRedeemed ); // NOTE: Round down to underestimate the share proceeds. // // The LP gets the pro-rata amount of the collected proceeds. uint256 shareProceeds = withdrawalSharesRedeemed.mulDivDown( _withdrawPool.proceeds, readyToWithdraw_ ); // Apply the update to the withdrawal pool. _withdrawPool.readyToWithdraw = readyToWithdraw_ - withdrawalSharesRedeemed.toUint128(); _withdrawPool.proceeds -= shareProceeds.toUint128(); // Withdraw the share proceeds to the user. proceeds = _withdraw(shareProceeds, _vaultSharePrice, _options); // NOTE: Round up to make the check more conservative. // // Enforce the minimum user output per share. if (proceeds < _minOutputPerShare.mulUp(withdrawalSharesRedeemed)) { revert IHyperdrive.OutputLimit(); } return (proceeds, withdrawalSharesRedeemed); } /// @dev Distribute as much of the excess idle as possible to the withdrawal /// pool while holding the LP share price constant. /// @param _vaultSharePrice The current vault share price. /// @return A failure flag indicating if the calculation succeeded. function _distributeExcessIdleSafe( uint256 _vaultSharePrice ) internal returns (bool) { return _distributeExcessIdleSafe( _vaultSharePrice, LPMath.SHARE_PROCEEDS_MAX_ITERATIONS ); } /// @dev Distribute as much of the excess idle as possible to the withdrawal /// pool while holding the LP share price constant. /// @param _vaultSharePrice The current vault share price. /// @param _maxIterations The number of iterations to use in the Newton's /// method component of `_distributeExcessIdleSafe`. This defaults to /// `LPMath.SHARE_PROCEEDS_MAX_ITERATIONS` if the specified value is /// smaller than the constant. /// @return A failure flag indicating if the calculation succeeded. function _distributeExcessIdleSafe( uint256 _vaultSharePrice, uint256 _maxIterations ) internal returns (bool) { // If there are no withdrawal shares, then there is nothing to // distribute. uint256 withdrawalSharesTotalSupply = _totalSupply[ AssetId._WITHDRAWAL_SHARE_ASSET_ID ] - _withdrawPool.readyToWithdraw; if (withdrawalSharesTotalSupply == 0) { return true; } // If there is no excess idle, then there is nothing to distribute. uint256 idle = _calculateIdleShareReserves(_vaultSharePrice); if (idle == 0) { return true; } // Get the distribute excess idle parameters. If this fails for some // we return a failure flag so that the caller can handle the failure. ( LPMath.DistributeExcessIdleParams memory params, bool success ) = _getDistributeExcessIdleParamsSafe( idle, withdrawalSharesTotalSupply, _vaultSharePrice ); if (!success) { return false; } // Calculate the amount of withdrawal shares that should be redeemed // and their share proceeds. (uint256 withdrawalSharesRedeemed, uint256 shareProceeds) = LPMath .calculateDistributeExcessIdle(params, _maxIterations); // Remove the withdrawal pool proceeds from the reserves. success = _updateLiquiditySafe(-shareProceeds.toInt256()); if (!success) { return false; } // Update the withdrawal pool's state. _withdrawPool.readyToWithdraw += withdrawalSharesRedeemed.toUint128(); _withdrawPool.proceeds += shareProceeds.toUint128(); return true; } /// @dev Updates the pool's liquidity and holds the pool's spot price /// constant. /// @param _shareReservesDelta The delta that should be applied to share /// reserves. function _updateLiquidity(int256 _shareReservesDelta) internal { // Attempt updating the pool's liquidity and revert if the update fails. if (!_updateLiquiditySafe(_shareReservesDelta)) { revert IHyperdrive.UpdateLiquidityFailed(); } } /// @dev Updates the pool's liquidity and holds the pool's spot price /// constant. /// @param _shareReservesDelta The delta that should be applied to share /// reserves. /// @return A flag indicating if the update succeeded. function _updateLiquiditySafe( int256 _shareReservesDelta ) internal returns (bool) { // Calculate the updated reserves and return false if the calculation fails. uint256 shareReserves_ = _marketState.shareReserves; int256 shareAdjustment_ = _marketState.shareAdjustment; uint256 bondReserves_ = _marketState.bondReserves; ( uint256 updatedShareReserves, int256 updatedShareAdjustment, uint256 updatedBondReserves, bool success ) = LPMath.calculateUpdateLiquiditySafe( shareReserves_, shareAdjustment_, bondReserves_, _minimumShareReserves, _shareReservesDelta ); if (!success) { return false; } // Update the market state and return true since the update was successful. if (updatedShareReserves != shareReserves_) { _marketState.shareReserves = updatedShareReserves.toUint128(); } if (updatedShareAdjustment != shareAdjustment_) { _marketState.shareAdjustment = updatedShareAdjustment.toInt128(); } if (updatedBondReserves != bondReserves_) { _marketState.bondReserves = updatedBondReserves.toUint128(); } return true; } }
// SPDX-License-Identifier: Apache-2.0 pragma solidity 0.8.22; import { IHyperdrive } from "../interfaces/IHyperdrive.sol"; import { IHyperdriveEvents } from "../interfaces/IHyperdriveEvents.sol"; import { HyperdriveBase } from "./HyperdriveBase.sol"; /// @author DELV /// @title HyperdriveMultiToken /// @notice Implements the MultiToken accounting that Hyperdrive uses to track /// user's positions. MultiToken maintains a set of balances and /// approvals for a list of sub-tokens specified by an asset ID. This /// token is mostly ERC1155 compliant; however, we remove on transfer /// callbacks and safe transfer because of the risk of external calls to /// untrusted code. /// @dev Our architecture maintains ERC20 compatibility by allowing users to /// access their balances and approvals through ERC20 forwarding contracts /// deployed by the registered forwarder factory. To ensure that only the /// ERC20 forwarders can call the bridge endpoints, we verify that the /// create2 pre-image of the caller address is the ERC20 forwarder bytecode /// and the token ID. /// @custom:disclaimer The language used in this code is for coding convenience /// only, and is not intended to, and does not, have any /// particular legal or regulatory significance. abstract contract HyperdriveMultiToken is IHyperdriveEvents, HyperdriveBase { /// @notice This modifier checks the caller is the create2 validated /// ERC20 bridge. /// @param tokenID The internal token identifier. modifier onlyLinker(uint256 tokenID) { // If the caller does not match the address hash, we revert because it // is not allowed to access permissioned methods. if (msg.sender != _deriveForwarderAddress(tokenID)) { revert IHyperdrive.InvalidERC20Bridge(); } // Execute the following function. _; } /// @dev Transfers several assets from one account to another. /// @param from The source account. /// @param to The destination account. /// @param ids The array of token ids of the asset to transfer. /// @param values The amount of each token to transfer. function _batchTransferFrom( address from, address to, uint256[] calldata ids, uint256[] calldata values ) internal { // Checks for inconsistent addresses. if (from == address(0) || to == address(0)) { revert IHyperdrive.RestrictedZeroAddress(); } // Check for inconsistent length. if (ids.length != values.length) { revert IHyperdrive.BatchInputLengthMismatch(); } // Call internal transfer for each asset. for (uint256 i = 0; i < ids.length; i++) { _transferFrom(ids[i], from, to, values[i], msg.sender); } } /// @dev Performs the actual transfer logic. /// @param tokenID The token identifier. /// @param from The address whose balance will be reduced. /// @param to The address whose balance will be increased. /// @param amount The amount of token to move. /// @param caller The msg.sender or the caller of the ERC20Forwarder. function _transferFrom( uint256 tokenID, address from, address to, uint256 amount, address caller ) internal { // Checks for inconsistent addresses. if (from == address(0) || to == address(0)) { revert IHyperdrive.RestrictedZeroAddress(); } // If the transaction sender is calling no need for further validation. if (caller != from) { // Or if the transaction sender can access all user assets, no need // for more validation. if (!_isApprovedForAll[from][caller]) { // Finally we load the per asset approval. uint256 approved = _perTokenApprovals[tokenID][from][caller]; // If it is not an infinite approval if (approved != type(uint256).max) { // Then we subtract the amount the caller wants to use // from how much they can use, reverting on underflow. // NOTE: This reverts without message for unapproved callers // when debugging that's the likely source of any mystery // reverts. _perTokenApprovals[tokenID][from][caller] -= amount; } } } // Reaching this point implies the transfer is authorized so we remove // from the source and add to the destination. _balanceOf[tokenID][from] -= amount; _balanceOf[tokenID][to] += amount; emit TransferSingle(caller, from, to, tokenID, amount); } /// @notice Sets the approval for a sub-token. /// @param tokenID The asset to approve the use of. /// @param operator The address who will be able to use the tokens. /// @param amount The max tokens the approved person can use, setting to /// uint256.max will cause the value to never decrement /// [saving gas on transfer]. /// @param caller The eth address which initiated the approval call. function _setApproval( uint256 tokenID, address operator, uint256 amount, address caller ) internal { _perTokenApprovals[tokenID][caller][operator] = amount; // Emit an event to track approval. emit Approval(caller, operator, amount); } /// @notice Minting function to create tokens. /// @param tokenID The asset type to create. /// @param to The address whose balance to increase. /// @param amount The number of tokens to create. /// @dev Must be used from inheriting contracts. function _mint( uint256 tokenID, address to, uint256 amount ) internal virtual { _balanceOf[tokenID][to] += amount; _totalSupply[tokenID] += amount; // Emit an event to track minting. emit TransferSingle(msg.sender, address(0), to, tokenID, amount); } /// @notice Burning function to remove tokens. /// @param tokenID The asset type to remove. /// @param from The address whose balance to decrease. /// @param amount The number of tokens to remove. /// @dev Must be used from inheriting contracts. function _burn(uint256 tokenID, address from, uint256 amount) internal { // Check to see if the balance is sufficient. If it isn't, throw an // insufficient balance error. if (_balanceOf[tokenID][from] < amount) { revert IHyperdrive.InsufficientBalance(); } // Decrement from the source and supply. unchecked { _balanceOf[tokenID][from] -= amount; } _totalSupply[tokenID] -= amount; // Emit an event to track burning. emit TransferSingle(msg.sender, from, address(0), tokenID, amount); } /// @dev Allows a caller who is not the owner of an account to execute the /// functionality of 'approve' for all assets with the owners signature. /// @param domainSeparator The EIP712 domain separator for this contract. /// @param permitTypehash The EIP712 typehash for the permit data. /// @param owner The owner of the account which is having the new approval set. /// @param spender The address which will be allowed to spend owner's tokens. /// @param _approved A boolean of the approval status to set to. /// @param deadline The timestamp which the signature must be submitted by /// to be valid. /// @param v Extra ECDSA data which allows public key recovery from /// signature assumed to be 27 or 28. /// @param r The r component of the ECDSA signature. /// @param s The s component of the ECDSA signature. /// @dev The signature for this function follows EIP 712 standard and should /// be generated with the eth_signTypedData JSON RPC call instead of /// the eth_sign JSON RPC call. If using out of date parity signing /// libraries the v component may need to be adjusted. Also it is very /// rare but possible for v to be other values, those values are not /// supported. function _permitForAll( bytes32 domainSeparator, bytes32 permitTypehash, address owner, address spender, bool _approved, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) internal { // Require that the signature is not expired. if (block.timestamp > deadline) { revert IHyperdrive.ExpiredDeadline(); } // Require that the owner is not zero. if (owner == address(0)) { revert IHyperdrive.RestrictedZeroAddress(); } // Check that the signature is valid and recovers to the owner. bytes32 structHash = keccak256( abi.encodePacked( "\x19\x01", domainSeparator, keccak256( abi.encode( permitTypehash, owner, spender, _approved, _nonces[owner], deadline ) ) ) ); address signer = ecrecover(structHash, v, r, s); if (signer != owner) { revert IHyperdrive.InvalidSignature(); } // Increment the signature nonce. unchecked { ++_nonces[owner]; } // Set the state. _isApprovedForAll[owner][spender] = _approved; // Emit an event to track approval. emit ApprovalForAll(owner, spender, _approved); } /// @notice Derive the ERC20 forwarder address for a provided `tokenId`. /// @param tokenId Token Id of the token whose forwarder contract address /// need to derived. /// @return Address of the ERC20 forwarder contract. function _deriveForwarderAddress( uint256 tokenId ) internal view returns (address) { // Get the salt which is used by the deploying contract. bytes32 salt = keccak256(abi.encode(address(this), tokenId)); // Perform the hash which determines the address of a create2 deployment. bytes32 addressBytes = keccak256( abi.encodePacked( bytes1(0xff), _linkerFactory, salt, _linkerCodeHash ) ); return address(uint160(uint256(addressBytes))); } }
// SPDX-License-Identifier: Apache-2.0 pragma solidity 0.8.22; import { IHyperdrive } from "../interfaces/IHyperdrive.sol"; import { IHyperdriveEvents } from "../interfaces/IHyperdriveEvents.sol"; import { AssetId } from "../libraries/AssetId.sol"; import { Errors } from "../libraries/Errors.sol"; import { FixedPointMath, ONE } from "../libraries/FixedPointMath.sol"; import { HyperdriveMath } from "../libraries/HyperdriveMath.sol"; import { LPMath } from "../libraries/LPMath.sol"; import { SafeCast } from "../libraries/SafeCast.sol"; import { HyperdriveLP } from "./HyperdriveLP.sol"; /// @author DELV /// @title HyperdriveShort /// @notice Implements the short accounting for Hyperdrive. /// @custom:disclaimer The language used in this code is for coding convenience /// only, and is not intended to, and does not, have any /// particular legal or regulatory significance. abstract contract HyperdriveShort is IHyperdriveEvents, HyperdriveLP { using FixedPointMath for uint256; using FixedPointMath for int256; using SafeCast for uint256; using SafeCast for int256; /// @dev Opens a short position. /// @param _bondAmount The amount of bonds to short. /// @param _maxDeposit The most the user expects to deposit for this trade. /// The units of this quantity are either base or vault shares, /// depending on the value of `_options.asBase`. /// @param _minVaultSharePrice The minimum vault share price at which to open /// the short. This allows traders to protect themselves from opening /// a short in a checkpoint where negative interest has accrued. /// @param _options The options that configure how the trade is settled. /// @return The maturity time of the short. /// @return The amount the user deposited for this trade. The units of this /// quantity are either base or vault shares, depending on the value /// of `_options.asBase`. function _openShort( uint256 _bondAmount, uint256 _maxDeposit, uint256 _minVaultSharePrice, IHyperdrive.Options calldata _options ) internal nonReentrant isNotPaused returns (uint256, uint256) { // Check that the message value is valid. _checkMessageValue(); // Check that the provided options are valid. _checkOptions(_options); // Ensure that the bond amount is greater than or equal to the minimum // transaction amount. if (_bondAmount < _minimumTransactionAmount) { revert IHyperdrive.MinimumTransactionAmount(); } // Perform a checkpoint and compute the amount of interest the short // would have received if they opened at the beginning of the checkpoint. // Since the short will receive interest from the beginning of the // checkpoint, they will receive this backdated interest back at closing. uint256 vaultSharePrice = _pricePerVaultShare(); if (vaultSharePrice < _minVaultSharePrice) { revert IHyperdrive.MinimumSharePrice(); } uint256 latestCheckpoint = _latestCheckpoint(); uint256 openVaultSharePrice = _applyCheckpoint( _latestCheckpoint(), vaultSharePrice, LPMath.SHARE_PROCEEDS_MAX_ITERATIONS, true ); // Calculate the pool and user deltas using the trading function. We // backdate the bonds sold to the beginning of the checkpoint. // Note: All state deltas are derived from the external function input. uint256 maturityTime = latestCheckpoint + _positionDuration; uint256 baseDeposit; uint256 shareReservesDelta; uint256 totalGovernanceFee; { uint256 spotPrice; ( baseDeposit, shareReservesDelta, totalGovernanceFee, spotPrice ) = _calculateOpenShort( _bondAmount, vaultSharePrice, openVaultSharePrice ); // Attribute the governance fees. _governanceFeesAccrued += totalGovernanceFee; // Update the weighted spot price. _updateWeightedSpotPrice( latestCheckpoint, block.timestamp, spotPrice ); } // Take custody of the trader's deposit and ensure that the trader // doesn't pay more than their max deposit. The trader's deposit is // equal to the proceeds that they would receive if they closed // immediately (without fees). Trader deposit is created to ensure that // the input to _deposit is denominated according to _options. // // NOTE: We don't check the maxDeposit against the output of deposit // because slippage from a deposit could cause a larger deposit taken // from the user to pass due to the shares being worth less after deposit. uint256 deposit = _convertToOptionFromBase( baseDeposit, vaultSharePrice, _options ); if (_maxDeposit < deposit) { revert IHyperdrive.OutputLimit(); } _deposit(deposit, _options); // Apply the state updates caused by opening the short. // Note: Updating the state using the result using the // deltas calculated from function inputs is consistent with // openLong. uint256 bondAmount = _bondAmount; // Avoid stack too deep error. _applyOpenShort( bondAmount, shareReservesDelta, vaultSharePrice, maturityTime ); // Mint the short tokens to the trader. uint256 assetId = AssetId.encodeAssetId( AssetId.AssetIdPrefix.Short, maturityTime ); IHyperdrive.Options calldata options = _options; // Avoid stack too deep error. _mint(assetId, options.destination, bondAmount); // Emit an OpenShort event. uint256 shareReservesDelta_ = shareReservesDelta; // Avoid stack too deep error. uint256 vaultSharePrice_ = vaultSharePrice; // Avoid stack too deep error. uint256 totalGovernanceFee_ = totalGovernanceFee; // Avoid stack too deep error. emit OpenShort( options.destination, assetId, maturityTime, deposit, vaultSharePrice_, options.asBase, // NOTE: We subtract out the governance fee from the share reserves // delta since the user is responsible for paying the governance // fee. (shareReservesDelta_ - totalGovernanceFee_).mulDown( vaultSharePrice_ ), bondAmount, options.extraData ); return (maturityTime, deposit); } /// @dev Closes a short position with a specified maturity time. /// @param _maturityTime The maturity time of the short. /// @param _bondAmount The amount of shorts to close. /// @param _minOutput The minimum output of this trade. The units of this /// quantity are either base or vault shares, depending on the value /// of `_options.asBase`. /// @param _options The options that configure how the trade is settled. /// @return The proceeds of closing this short. The units of this quantity /// are either base or vault shares, depending on the value of /// `_options.asBase`. function _closeShort( uint256 _maturityTime, uint256 _bondAmount, uint256 _minOutput, IHyperdrive.Options calldata _options ) internal nonReentrant returns (uint256) { // Check that the provided options are valid. _checkOptions(_options); // Ensure that the bond amount is greater than or equal to the minimum // transaction amount. if (_bondAmount < _minimumTransactionAmount) { revert IHyperdrive.MinimumTransactionAmount(); } // If the short hasn't matured, we checkpoint the latest checkpoint. // Otherwise, we perform a checkpoint at the time the short matured. // This ensures the short and all of the other positions in the // checkpoint are closed. uint256 vaultSharePrice = _pricePerVaultShare(); if (block.timestamp < _maturityTime) { _applyCheckpoint( _latestCheckpoint(), vaultSharePrice, LPMath.SHARE_PROCEEDS_MAX_ITERATIONS, true ); } else { _applyCheckpoint( _maturityTime, vaultSharePrice, LPMath.SHARE_PROCEEDS_MAX_ITERATIONS, true ); } // Burn the shorts that are being closed. _burn( AssetId.encodeAssetId(AssetId.AssetIdPrefix.Short, _maturityTime), msg.sender, _bondAmount ); // Calculate the changes to the reserves and the traders proceeds up // front. This will also verify that the calculated values don't break // any invariants. // Note: All state deltas are derived from the external function input. ( uint256 bondReservesDelta, uint256 shareProceeds, uint256 shareReservesDelta, int256 shareAdjustmentDelta, uint256 totalGovernanceFee, uint256 spotPrice ) = _calculateCloseShort(_bondAmount, vaultSharePrice, _maturityTime); // If the position hasn't matured, apply the accounting updates that // result from closing the short to the reserves and pay out the // withdrawal pool if necessary. uint256 maturityTime = _maturityTime; // Avoid stack too deep error. if (block.timestamp < _maturityTime) { // Attribute the governance fees. _governanceFeesAccrued += totalGovernanceFee; // Update the weighted spot price. _updateWeightedSpotPrice( _latestCheckpoint(), block.timestamp, spotPrice ); // Update the pool's state to account for the short being closed. _applyCloseShort( _bondAmount, bondReservesDelta, shareReservesDelta, shareAdjustmentDelta, maturityTime ); // Update the global long exposure. Since we're closing a short, the // number of non-netted longs increases by the bond amount. int256 nonNettedLongs = _nonNettedLongs(_maturityTime); _updateLongExposure( nonNettedLongs - _bondAmount.toInt256(), nonNettedLongs ); // Ensure that the system is still solvent after closing the shorts. // Closing shorts increases the share reserves, but it also // increases the long exposure. if (!_isSolvent(vaultSharePrice)) { Errors.throwInsufficientLiquidityError(); } // Distribute the excess idle to the withdrawal pool. If the // distribute excess idle calculation fails, we revert to avoid // putting the system in an unhealthy state after the trade is // processed. bool success = _distributeExcessIdleSafe(vaultSharePrice); if (!success) { revert IHyperdrive.DistributeExcessIdleFailed(); } } else { // Apply the zombie close to the state and adjust the share proceeds // to account for negative interest that might have accrued to the // zombie share reserves. shareProceeds = _applyZombieClose(shareProceeds, vaultSharePrice); // Distribute the excess idle to the withdrawal pool. If the // distribute excess idle calculation fails, we proceed with the // calculation since traders should be able to close their positions // at maturity regardless of whether idle could be distributed. _distributeExcessIdleSafe(vaultSharePrice); } // Withdraw the profit to the trader. This includes the proceeds from // the short sale as well as the variable interest that was collected // on the face value of the bonds. uint256 proceeds = _withdraw(shareProceeds, vaultSharePrice, _options); // Enforce the user's minimum output. // // NOTE: We use the value that is returned from the withdraw to check // against the minOutput because in the event of slippage on the // withdraw, we want it to be caught be the minOutput check. if (proceeds < _minOutput) { revert IHyperdrive.OutputLimit(); } // Emit a CloseShort event. uint256 bondAmount = _bondAmount; // Avoid stack too deep error. uint256 shareReservesDelta_ = shareReservesDelta; // Avoid stack too deep error. uint256 totalGovernanceFee_ = totalGovernanceFee; // Avoid stack too deep error. uint256 vaultSharePrice_ = vaultSharePrice; // Avoid stack too deep error. IHyperdrive.Options calldata options = _options; // Avoid stack too deep error. emit CloseShort( msg.sender, // trader options.destination, // destination AssetId.encodeAssetId(AssetId.AssetIdPrefix.Short, maturityTime), maturityTime, proceeds, vaultSharePrice_, options.asBase, // NOTE: We add the governance fee to the share reserves delta since // the user is responsible for paying the governance fee. (shareReservesDelta_ + totalGovernanceFee_).mulDown( vaultSharePrice_ ), bondAmount, options.extraData ); return proceeds; } /// @dev Applies an open short to the state. This includes updating the /// reserves and maintaining the reserve invariants. /// @param _bondAmount The amount of bonds shorted. /// @param _shareReservesDelta The amount of shares paid to the curve. /// @param _vaultSharePrice The current vault share price. /// @param _maturityTime The maturity time of the long. function _applyOpenShort( uint256 _bondAmount, uint256 _shareReservesDelta, uint256 _vaultSharePrice, uint256 _maturityTime ) internal { // If the share reserves would underflow when the short is opened, then // we revert with an insufficient liquidity error. uint256 shareReserves = _marketState.shareReserves; if (shareReserves < _shareReservesDelta) { Errors.throwInsufficientLiquidityError(); } unchecked { shareReserves -= _shareReservesDelta; } // The share reserves are decreased in this operation, so we need to // verify that our invariants that z >= z_min and z - zeta >= z_min // are satisfied. The former is checked when we check solvency (since // global exposure is greater than or equal to zero, z < z_min // implies z - e/c - z_min < 0. if ( HyperdriveMath.calculateEffectiveShareReserves( shareReserves, _marketState.shareAdjustment ) < _minimumShareReserves ) { Errors.throwInsufficientLiquidityError(); } // Update the average maturity time of short positions. _marketState.shortAverageMaturityTime = uint256( _marketState.shortAverageMaturityTime ) .updateWeightedAverage( _marketState.shortsOutstanding, _maturityTime * ONE, // scale up to fixed point scale _bondAmount, true ) .toUint128(); // Apply the trading deltas to the reserves and increase the bond buffer // by the amount of bonds that were shorted. We don't need to add the // margin or pre-paid interest to the reserves because of the way that // the close short accounting works. _marketState.shareReserves = shareReserves.toUint128(); _marketState.bondReserves += _bondAmount.toUint128(); _marketState.shortsOutstanding += _bondAmount.toUint128(); // Update the global long exposure. Since we're opening a short, the // number of non-netted longs decreases by the bond amount. int256 nonNettedLongs = _nonNettedLongs(_maturityTime); _updateLongExposure( nonNettedLongs, nonNettedLongs - _bondAmount.toInt256() ); // Opening a short decreases the system's exposure because the short's // margin can be used to offset some of the long exposure. Despite this, // opening a short decreases the share reserves, which limits the amount // of capital available to back non-netted long exposure. Since both // quantities decrease, we need to check that the system is still solvent. if (!_isSolvent(_vaultSharePrice)) { Errors.throwInsufficientLiquidityError(); } // Distribute the excess idle to the withdrawal pool. If the distribute // excess idle calculation fails, we revert to avoid putting the system // in an unhealthy state after the trade is processed. bool success = _distributeExcessIdleSafe(_vaultSharePrice); if (!success) { revert IHyperdrive.DistributeExcessIdleFailed(); } } /// @dev Applies the trading deltas from a closed short to the reserves and /// the withdrawal pool. /// @param _bondAmount The amount of shorts that were closed. /// @param _bondReservesDelta The amount of bonds removed from the reserves. /// @param _shareReservesDelta The amount of shares added to the reserves. /// @param _shareAdjustmentDelta The amount to increase the share adjustment. /// @param _maturityTime The maturity time of the short. function _applyCloseShort( uint256 _bondAmount, uint256 _bondReservesDelta, uint256 _shareReservesDelta, int256 _shareAdjustmentDelta, uint256 _maturityTime ) internal { // Update the short average maturity time. uint128 shortsOutstanding_ = _marketState.shortsOutstanding; _marketState.shortAverageMaturityTime = uint256( _marketState.shortAverageMaturityTime ) .updateWeightedAverage( shortsOutstanding_, _maturityTime * ONE, // scale up to fixed point scale _bondAmount, false ) .toUint128(); // Decrease the amount of shorts outstanding. _marketState.shortsOutstanding = shortsOutstanding_ - _bondAmount.toUint128(); // Update the reserves and the share adjustment. _marketState.shareReserves += _shareReservesDelta.toUint128(); _marketState.shareAdjustment += _shareAdjustmentDelta.toInt128(); _marketState.bondReserves -= _bondReservesDelta.toUint128(); } /// @dev Calculate the pool reserve and trader deltas that result from /// opening a short. This calculation includes trading fees. /// @param _bondAmount The amount of bonds being sold to open the short. /// @param _vaultSharePrice The current vault share price. /// @param _openVaultSharePrice The vault share price at the beginning of /// the checkpoint. /// @return baseDeposit The deposit, in base, required to open the short. /// @return shareReservesDelta The change in the share reserves. /// @return totalGovernanceFee The governance fee in shares. function _calculateOpenShort( uint256 _bondAmount, uint256 _vaultSharePrice, uint256 _openVaultSharePrice ) internal view returns ( uint256 baseDeposit, uint256 shareReservesDelta, uint256 totalGovernanceFee, uint256 spotPrice ) { // Calculate the effect that opening the short should have on the pool's // reserves as well as the amount of shares the trader receives from // selling the shorted bonds at the market price. uint256 effectiveShareReserves = _effectiveShareReserves(); shareReservesDelta = HyperdriveMath.calculateOpenShort( effectiveShareReserves, _marketState.bondReserves, _bondAmount, _timeStretch, _vaultSharePrice, _initialVaultSharePrice ); // NOTE: Round up to make the check stricter. // // If the base proceeds of selling the bonds is greater than the bond // amount, then the trade occurred in the negative interest domain. We // revert in these pathological cases. if (shareReservesDelta.mulUp(_vaultSharePrice) > _bondAmount) { Errors.throwInsufficientLiquidityError(); } // Calculate the current spot price. uint256 curveFee; uint256 governanceCurveFee; spotPrice = HyperdriveMath.calculateSpotPrice( effectiveShareReserves, _marketState.bondReserves, _initialVaultSharePrice, _timeStretch ); // Calculate the fees charged to the user (curveFee) and the portion // of those fees that are paid to governance (governanceCurveFee). (curveFee, , governanceCurveFee, ) = _calculateFeesGivenBonds( _bondAmount, ONE, // shorts are opened at the beginning of the term spotPrice, _vaultSharePrice ); // Subtract the total curve fee minus the governance curve fee to the // amount that will be subtracted from the share reserves. This ensures // that the LPs are credited with the fee the trader paid on the // curve trade minus the portion of the curve fee that was paid to // governance. // // shareReservesDelta, curveFee and governanceCurveFee are all // denominated in shares so we just need to subtract out the // governanceCurveFee from the shareReservesDelta since that fee isn't // reserved for the LPs. // // shares -= shares - shares shareReservesDelta -= curveFee - governanceCurveFee; // NOTE: Round up to overestimate the base deposit. // // The trader will need to deposit capital to pay for the fixed rate, // the curve fee, the flat fee, and any back-paid interest that will be // received back upon closing the trade. If negative interest has // accrued during the current checkpoint, we set the close vault share // price to equal the open vault share price. This ensures that shorts // don't benefit from negative interest that accrued during the current // checkpoint. uint256 vaultSharePrice = _vaultSharePrice; // avoid stack-too-deep baseDeposit = HyperdriveMath .calculateShortProceedsUp( _bondAmount, // NOTE: We subtract the governance fee back to the share // reserves delta here because the trader will need to provide // this in their deposit. shareReservesDelta - governanceCurveFee, _openVaultSharePrice, vaultSharePrice.max(_openVaultSharePrice), vaultSharePrice, _flatFee ) .mulUp(_vaultSharePrice); return (baseDeposit, shareReservesDelta, governanceCurveFee, spotPrice); } /// @dev Calculate the pool reserve and trader deltas that result from /// closing a short. This calculation includes trading fees. /// @param _bondAmount The amount of bonds being purchased to close the /// short. /// @param _vaultSharePrice The current vault share price. /// @param _maturityTime The maturity time of the short position. /// @return bondReservesDelta The change in the bond reserves. /// @return shareProceeds The proceeds in shares of closing the short. /// @return shareReservesDelta The shares added to the reserves. /// @return shareAdjustmentDelta The change in the share adjustment. /// @return totalGovernanceFee The governance fee in shares. function _calculateCloseShort( uint256 _bondAmount, uint256 _vaultSharePrice, uint256 _maturityTime ) internal view returns ( uint256 bondReservesDelta, uint256 shareProceeds, uint256 shareReservesDelta, int256 shareAdjustmentDelta, uint256 totalGovernanceFee, uint256 spotPrice ) { // Calculate the effect that closing the short should have on the pool's // reserves as well as the amount of shares the trader pays to buy the // bonds that they shorted back at the market price. uint256 shareCurveDelta; uint256 effectiveShareReserves = _effectiveShareReserves(); { // Calculate the effect that closing the short should have on the // pool's reserves as well as the amount of shares the trader needs // to pay to purchase the shorted bonds at the market price. // // NOTE: We calculate the time remaining from the latest checkpoint // to ensure that opening/closing a position doesn't result in // immediate profit. uint256 timeRemaining = _calculateTimeRemaining(_maturityTime); uint256 bondAmount = _bondAmount; // Avoid stack too deep. uint256 vaultSharePrice = _vaultSharePrice; // Avoid stack too deep. ( shareCurveDelta, bondReservesDelta, shareReservesDelta ) = HyperdriveMath.calculateCloseShort( effectiveShareReserves, _marketState.bondReserves, bondAmount, timeRemaining, _timeStretch, vaultSharePrice, _initialVaultSharePrice ); // Ensure that the trader didn't purchase bonds at a negative interest // rate after accounting for fees. spotPrice = HyperdriveMath.calculateSpotPrice( effectiveShareReserves, _marketState.bondReserves, _initialVaultSharePrice, _timeStretch ); if ( _isNegativeInterest( shareCurveDelta, bondReservesDelta, HyperdriveMath.calculateCloseShortMaxSpotPrice( spotPrice, _curveFee ) ) ) { Errors.throwInsufficientLiquidityError(); } // Calculate the fees charged to the user (curveFee and // flatFee) and the portion of those fees that are paid to // governance (totalGovernanceFee). uint256 curveFee; uint256 flatFee; uint256 governanceCurveFee; ( curveFee, flatFee, governanceCurveFee, totalGovernanceFee ) = _calculateFeesGivenBonds( bondAmount, timeRemaining, spotPrice, vaultSharePrice ); // Add the total curve fee minus the governance curve fee to the // amount that will be added to the share reserves. This ensures // that the LPs are credited with the fee the trader paid on the // curve trade minus the portion of the curve fee that was paid to // governance. // // shareCurveDelta, curveFee and governanceCurveFee are all // denominated in shares so we just need to subtract out the // governanceCurveFees from the shareCurveDelta since that fee isn't // reserved for the LPs shareCurveDelta += curveFee - governanceCurveFee; // Calculate the shareReservesDelta that the user must make to close // out the short. We add the curveFee (shares) and flatFee (shares) // to the shareReservesDelta to ensure that fees are collected. shareReservesDelta += curveFee + flatFee; } // Calculate the share proceeds owed to the short and account for // negative interest that accrued over the period. { uint256 openVaultSharePrice = _checkpoints[ _maturityTime - _positionDuration ].vaultSharePrice; uint256 closeVaultSharePrice = block.timestamp < _maturityTime ? _vaultSharePrice : _checkpoints[_maturityTime].vaultSharePrice; // NOTE: Round down to underestimate the short proceeds. // // Calculate the share proceeds owed to the short. We calculate this // before scaling the share payment for negative interest. Shorts // are responsible for paying for 100% of the negative interest, so // they aren't benefited when the payment to LPs is decreased due to // negative interest. Similarly, the governance fee is included in // the share payment. The LPs don't receive the governance fee, but // the short is responsible for paying it. uint256 vaultSharePrice = _vaultSharePrice; // Avoid stack too deep. shareProceeds = HyperdriveMath.calculateShortProceedsDown( _bondAmount, shareReservesDelta, openVaultSharePrice, closeVaultSharePrice, vaultSharePrice, _flatFee ); // The governance fee isn't included in the share payment that is // added to the share reserves. We remove it here to simplify the // accounting updates. shareReservesDelta -= totalGovernanceFee; // Ensure that the ending spot price is less than 1. if ( HyperdriveMath.calculateSpotPrice( effectiveShareReserves + shareCurveDelta, _marketState.bondReserves - bondReservesDelta, _initialVaultSharePrice, _timeStretch ) > ONE ) { Errors.throwInsufficientLiquidityError(); } // Adjust the computed proceeds and delta for negative interest. // We also compute the share adjustment delta at this step to ensure // that we don't break our AMM invariant when we account for negative // interest and flat adjustments. ( shareProceeds, shareReservesDelta, shareCurveDelta, shareAdjustmentDelta, totalGovernanceFee ) = HyperdriveMath.calculateNegativeInterestOnClose( shareProceeds, shareReservesDelta, shareCurveDelta, totalGovernanceFee, openVaultSharePrice, closeVaultSharePrice, false ); } return ( bondReservesDelta, shareProceeds, shareReservesDelta, shareAdjustmentDelta, totalGovernanceFee, spotPrice ); } }
// SPDX-License-Identifier: Apache-2.0 pragma solidity 0.8.22; import { ReentrancyGuard } from "openzeppelin/utils/ReentrancyGuard.sol"; import { IERC20 } from "../interfaces/IERC20.sol"; import { IHyperdrive } from "../interfaces/IHyperdrive.sol"; import { IHyperdriveAdminController } from "../interfaces/IHyperdriveAdminController.sol"; import { FixedPointMath } from "../libraries/FixedPointMath.sol"; /// @author DELV /// @title HyperdriveStorage /// @notice Hyperdrive's storage contract. /// @custom:disclaimer The language used in this code is for coding convenience /// only, and is not intended to, and does not, have any /// particular legal or regulatory significance. abstract contract HyperdriveStorage is ReentrancyGuard { using FixedPointMath for uint256; /// Metadata /// /// @dev The instance's name. string internal _name; /// Tokens /// /// @dev The base asset. IERC20 internal immutable _baseToken; /// @dev The vault shares asset. IERC20 internal immutable _vaultSharesToken; /// Time /// /// @dev The amount of seconds between vault share price checkpoints. uint256 internal immutable _checkpointDuration; /// @dev The amount of seconds that elapse before a bond can be redeemed. uint256 internal immutable _positionDuration; /// @dev A parameter that decreases slippage around a target rate. uint256 internal immutable _timeStretch; /// Fees /// /// @dev The LP fee applied to the curve portion of a trade. uint256 internal immutable _curveFee; /// @dev The LP fee applied to the flat portion of a trade. uint256 internal immutable _flatFee; /// @dev The portion of the LP fee that goes to governance. uint256 internal immutable _governanceLPFee; /// @dev The portion of the zombie interest that goes to governance. uint256 internal immutable _governanceZombieFee; /// Market State /// /// @dev The vault share price at the time the pool was created. uint256 internal immutable _initialVaultSharePrice; /// @dev The minimum amount of share reserves that must be maintained at all /// times. This is used to enforce practical limits on the share /// reserves to avoid numerical issues that can occur if the share /// reserves become very small or equal to zero. uint256 internal immutable _minimumShareReserves; /// @dev The minimum amount of tokens that a position can be opened or /// closed with. uint256 internal immutable _minimumTransactionAmount; /// @dev The maximum delta between the last checkpoint's weighted spot APR /// and the current spot APR for an LP to add liquidity. This protects /// LPs from sandwich attacks. uint256 internal immutable _circuitBreakerDelta; /// @dev The state of the market. This includes the reserves, buffers, and /// other data used to price trades and maintain solvency. IHyperdrive.MarketState internal _marketState; /// @dev The state corresponding to the withdraw pool. IHyperdrive.WithdrawPool internal _withdrawPool; /// @dev Hyperdrive positions are bucketed into checkpoints, which allows us /// to avoid poking in any period that has LP or trading activity. The /// checkpoints contain the starting vault share price from the /// checkpoint as well as aggregate volume values. mapping(uint256 checkpointNumber => IHyperdrive.Checkpoint checkpoint) internal _checkpoints; /// Admin /// // TODO: This isn't included in `IHyperdrive.PoolConfig` for backwards // compatability, but we should add it in a future breaking version. // /// @dev The address that contains the admin configuration for this instance. /// @dev A HyperdriveFactory instance can serve as the admin controller. IHyperdriveAdminController internal immutable _adminController; /// @dev Governance fees that haven't been collected yet denominated in shares. uint256 internal _governanceFeesAccrued; /// MultiToken /// /// @dev The forwarder factory that deploys ERC20 forwarders for this /// instance. address internal immutable _linkerFactory; /// @dev The bytecode hash of the contract which forwards purely ERC20 calls /// to this contract. bytes32 internal immutable _linkerCodeHash; /// @dev Allows loading of each balance. mapping(uint256 tokenId => mapping(address user => uint256 balance)) internal _balanceOf; /// @dev Allows loading of each total supply. mapping(uint256 tokenId => uint256 supply) internal _totalSupply; /// @dev Uniform approval for all tokens. mapping(address from => mapping(address caller => bool isApproved)) internal _isApprovedForAll; /// @dev Additional optional per token approvals. This is non-standard for /// ERC1155, but it's necessary to replicate the ERC20 interface. mapping(uint256 tokenId => mapping(address from => mapping(address caller => uint256 approved))) internal _perTokenApprovals; /// @dev A mapping to track the permitForAll signature nonces. mapping(address user => uint256 nonce) internal _nonces; /// Constructor /// // TODO: It would be good to update `PoolConfig` when we make other breaking // changes. // /// @notice Instantiates Hyperdrive's storage. /// @param _config The configuration of the Hyperdrive pool. /// @param __adminController The admin controller that will specify the /// admin parameters for this contract. constructor( IHyperdrive.PoolConfig memory _config, IHyperdriveAdminController __adminController ) { // Initialize the base and vault shares token addresses. _baseToken = _config.baseToken; _vaultSharesToken = _config.vaultSharesToken; // Initialize the initial vault share price. _initialVaultSharePrice = _config.initialVaultSharePrice; // Initialize the minimum share reserves. The minimum share reserves // defines the amount of shares that will be reserved to ensure that // the share reserves are never empty. We will also burn LP shares equal // to the minimum share reserves upon initialization to ensure that the // total supply of active LP tokens is always greater than zero. _minimumShareReserves = _config.minimumShareReserves; // Initialize the minimum transaction amount. The minimum transaction // amount defines the minimum input that the system will allow, which // prevents weird rounding issues that can occur with very small // amounts. _minimumTransactionAmount = _config.minimumTransactionAmount; // Initialize the maximum add liquidity APR delta. The maximum add // liquidity APR delta defines the maximum delta between the current // spot APR and the weighted spot APR from the last checkpoint for an // LP to add liquidity. This mitigates the possibility of LP sandwich // attacks by making them economically infeasible to pull off. _circuitBreakerDelta = _config.circuitBreakerDelta; // Initialize the time configurations. There must be at least one // checkpoint per term to avoid having a position duration of zero. _checkpointDuration = _config.checkpointDuration; _positionDuration = _config.positionDuration; _timeStretch = _config.timeStretch; // Initialize the fee parameters. _curveFee = _config.fees.curve; _flatFee = _config.fees.flat; _governanceLPFee = _config.fees.governanceLP; _governanceZombieFee = _config.fees.governanceZombie; // Initialize the MultiToken immutables. _linkerFactory = _config.linkerFactory; _linkerCodeHash = _config.linkerCodeHash; // Initialize the admin controller. _adminController = __adminController; } }
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; import { IHyperdrive } from "../interfaces/IHyperdrive.sol"; /// @author DELV /// @title Hyperdrive /// @notice A library that handles the encoding and decoding of asset IDs for /// Hyperdrive. /// @custom:disclaimer The language used in this code is for coding convenience /// only, and is not intended to, and does not, have any /// particular legal or regulatory significance. library AssetId { uint256 internal constant _LP_ASSET_ID = 0; uint256 internal constant _WITHDRAWAL_SHARE_ASSET_ID = uint256(AssetIdPrefix.WithdrawalShare) << 248; enum AssetIdPrefix { LP, Long, Short, WithdrawalShare } /// @dev Encodes a prefix and a timestamp into an asset ID. Asset IDs are /// used so that LP, long, and short tokens can all be represented in a /// single MultiToken instance. The zero asset ID indicates the LP /// token. /// @param _prefix A one byte prefix that specifies the asset type. /// @param _timestamp A timestamp associated with the asset. /// @return id The asset ID. function encodeAssetId( AssetIdPrefix _prefix, uint256 _timestamp ) internal pure returns (uint256 id) { // [identifier: 8 bits][timestamp: 248 bits] if ( _timestamp > 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff ) { revert IHyperdrive.InvalidTimestamp(); } assembly ("memory-safe") { id := or(shl(0xf8, _prefix), _timestamp) } } /// @dev Decodes an encoded asset ID into it's constituent parts of an /// identifier, data and a timestamp. /// @param _id The asset ID. /// @return _prefix A one byte prefix that specifies the asset type. /// @return _timestamp A timestamp associated with the asset. function decodeAssetId( uint256 _id ) internal pure returns (AssetIdPrefix _prefix, uint256 _timestamp) { // [identifier: 8 bits][timestamp: 248 bits] assembly ("memory-safe") { _prefix := shr(0xf8, _id) // shr 248 bits _timestamp := and( 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, _id ) // 248 bit-mask } } /// @dev Converts an asset ID to a token name. /// @param _id The asset ID. /// @return _name The token name. function assetIdToName( uint256 _id ) internal pure returns (string memory _name) { (AssetIdPrefix prefix, uint256 timestamp) = decodeAssetId(_id); string memory _timestamp = toString(timestamp); if (prefix == AssetIdPrefix.LP) { _name = "Hyperdrive LP"; } else if (prefix == AssetIdPrefix.Long) { _name = string(abi.encodePacked("Hyperdrive Long: ", _timestamp)); } else if (prefix == AssetIdPrefix.Short) { _name = string(abi.encodePacked("Hyperdrive Short: ", _timestamp)); } else if (prefix == AssetIdPrefix.WithdrawalShare) { _name = "Hyperdrive Withdrawal Share"; } } /// @dev Converts an asset ID to a token symbol. /// @param _id The asset ID. /// @return _symbol The token symbol. function assetIdToSymbol( uint256 _id ) internal pure returns (string memory _symbol) { (AssetIdPrefix prefix, uint256 timestamp) = decodeAssetId(_id); string memory _timestamp = toString(timestamp); if (prefix == AssetIdPrefix.LP) { _symbol = "HYPERDRIVE-LP"; } else if (prefix == AssetIdPrefix.Long) { _symbol = string(abi.encodePacked("HYPERDRIVE-LONG:", _timestamp)); } else if (prefix == AssetIdPrefix.Short) { _symbol = string(abi.encodePacked("HYPERDRIVE-SHORT:", _timestamp)); } else if (prefix == AssetIdPrefix.WithdrawalShare) { _symbol = "HYPERDRIVE-WS"; } } /// @dev Converts an unsigned integer to a string. /// @param _num The integer to be converted. /// @return result The stringified integer. function toString( uint256 _num ) internal pure returns (string memory result) { // We overallocate memory for the string. The maximum number of digits // that a uint256 can hold is log_10(2 ^ 256) which is approximately // 77.06. We round up so that we have space for the last digit. uint256 maxStringLength = 78; bytes memory rawResult = new bytes(maxStringLength); // Loop through the integer and add each digit to the raw result, // starting at the end of the string and working towards the beginning. uint256 digits = 0; for (; _num != 0; _num /= 10) { rawResult[maxStringLength - digits - 1] = bytes1( uint8((_num % 10) + 48) ); digits++; } // Point the string result to the beginning of the stringified integer // and update the length. assembly { result := add(rawResult, sub(maxStringLength, digits)) mstore(result, digits) } return result; } }
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; import { IHyperdrive } from "../interfaces/IHyperdrive.sol"; library Errors { /// @dev Throws an InsufficientLiquidity error. We do this in a helper /// function to reduce the code size. function throwInsufficientLiquidityError() internal pure { revert IHyperdrive.InsufficientLiquidity(); } }
/// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; import { IHyperdrive } from "../interfaces/IHyperdrive.sol"; import { SafeCast } from "./SafeCast.sol"; uint256 constant ONE = 1e18; /// @author DELV /// @title FixedPointMath /// @notice A fixed-point math library. /// @custom:disclaimer The language used in this code is for coding convenience /// only, and is not intended to, and does not, have any /// particular legal or regulatory significance. library FixedPointMath { using FixedPointMath for uint256; using SafeCast for uint256; uint256 internal constant MAX_UINT256 = 2 ** 256 - 1; /// @param x Fixed point number in 1e18 format. /// @param y Fixed point number in 1e18 format. /// @param denominator Fixed point number in 1e18 format. /// @return z The result of x * y / denominator rounded down. function mulDivDown( uint256 x, uint256 y, uint256 denominator ) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y)) if iszero( mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y))))) ) { revert(0, 0) } // Divide x * y by the denominator. z := div(mul(x, y), denominator) } } /// @param a Fixed point number in 1e18 format. /// @param b Fixed point number in 1e18 format. /// @return Result of a * b rounded down. function mulDown(uint256 a, uint256 b) internal pure returns (uint256) { return (mulDivDown(a, b, ONE)); } /// @param a Fixed point number in 1e18 format. /// @param b Fixed point number in 1e18 format. /// @return Result of a / b rounded down. function divDown(uint256 a, uint256 b) internal pure returns (uint256) { return (mulDivDown(a, ONE, b)); // Equivalent to (a * 1e18) / b rounded down. } /// @param x Fixed point number in 1e18 format. /// @param y Fixed point number in 1e18 format. /// @param denominator Fixed point number in 1e18 format. /// @return z The result of x * y / denominator rounded up. function mulDivUp( uint256 x, uint256 y, uint256 denominator ) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y)) if iszero( mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y))))) ) { revert(0, 0) } // If x * y modulo the denominator is strictly greater than 0, // 1 is added to round up the division of x * y by the denominator. z := add( gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator) ) } } /// @param a Fixed point number in 1e18 format. /// @param b Fixed point number in 1e18 format. /// @return The result of a * b rounded up. function mulUp(uint256 a, uint256 b) internal pure returns (uint256) { return (mulDivUp(a, b, ONE)); } /// @param a Fixed point number in 1e18 format. /// @param b Fixed point number in 1e18 format. /// @return The result of a / b rounded up. function divUp(uint256 a, uint256 b) internal pure returns (uint256) { return (mulDivUp(a, ONE, b)); } /// @dev Exponentiation (x^y) with unsigned 18 decimal fixed point base and exponent. /// @param x Fixed point number in 1e18 format. /// @param y Fixed point number in 1e18 format. /// @return The result of x^y. function pow(uint256 x, uint256 y) internal pure returns (uint256) { // If the exponent is 0, return 1. if (y == 0) { return ONE; } // If the base is 0, return 0. if (x == 0) { return 0; } // Using properties of logarithms we calculate x^y: // -> ln(x^y) = y * ln(x) // -> e^(y * ln(x)) = x^y int256 y_int256 = y.toInt256(); // solhint-disable-line var-name-mixedcase // Compute y*ln(x) // Any overflow for x will be caught in ln() in the initial bounds check int256 lnx = ln(x.toInt256()); int256 ylnx; assembly ("memory-safe") { ylnx := mul(y_int256, lnx) } ylnx /= int256(ONE); // Calculate exp(y * ln(x)) to get x^y return uint256(exp(ylnx)); } /// @dev Computes e^x in 1e18 fixed point. /// @dev Credit to Remco (https://github.com/recmo/experiment-solexp/blob/main/src/FixedPointMathLib.sol) /// @param x Fixed point number in 1e18 format. /// @return r The result of e^x. function exp(int256 x) internal pure returns (int256 r) { unchecked { // When the result is < 0.5 we return zero. This happens when // x <= floor(log(0.5e18) * 1e18) ~ -42e18 if (x <= -42139678854452767551) return 0; // When the result is > (2**255 - 1) / 1e18 we can not represent it as an // int. This happens when x >= floor(log((2**255 - 1) / 1e18) * 1e18) ~ 135. if (x >= 135305999368893231589) revert IHyperdrive.ExpInvalidExponent(); // x is now in the range (-42, 136) * 1e18. Convert to (-42, 136) * 2**96 // for more intermediate precision and a binary basis. This base conversion // is a multiplication by 1e18 / 2**96 = 5**18 / 2**78. x = (x << 78) / 5 ** 18; // Reduce range of x to (-½ ln 2, ½ ln 2) * 2**96 by factoring out powers // of two such that exp(x) = exp(x') * 2**k, where k is an integer. // Solving this gives k = round(x / log(2)) and x' = x - k * log(2). // Note: 54916777467707473351141471128 = 2^96 ln(2). int256 k = ((x << 96) / 54916777467707473351141471128 + 2 ** 95) >> 96; x = x - k * 54916777467707473351141471128; // k is in the range [-61, 195]. // Evaluate using a (6, 7)-term rational approximation. // p is made monic, we'll multiply by a scale factor later. int256 y = x + 1346386616545796478920950773328; y = ((y * x) >> 96) + 57155421227552351082224309758442; int256 p = y + x - 94201549194550492254356042504812; p = ((p * y) >> 96) + 28719021644029726153956944680412240; p = p * x + (4385272521454847904659076985693276 << 96); // We leave p in 2**192 basis so we don't need to scale it back up for the division. int256 q = x - 2855989394907223263936484059900; q = ((q * x) >> 96) + 50020603652535783019961831881945; q = ((q * x) >> 96) - 533845033583426703283633433725380; q = ((q * x) >> 96) + 3604857256930695427073651918091429; q = ((q * x) >> 96) - 14423608567350463180887372962807573; q = ((q * x) >> 96) + 26449188498355588339934803723976023; /// @solidity memory-safe-assembly assembly { // Div in assembly because solidity adds a zero check despite the unchecked. // The q polynomial won't have zeros in the domain as all its roots are complex. // No scaling is necessary because p is already 2**96 too large. r := sdiv(p, q) } // r should be in the range (0.09, 0.25) * 2**96. // We now need to multiply r by: // * the scale factor s = ~6.031367120. // * the 2**k factor from the range reduction. // * the 1e18 / 2**96 factor for base conversion. // We do this all at once, with an intermediate result in 2**213 // basis, so the final right shift is always by a positive amount. r = ((uint256(r) * 3822833074963236453042738258902158003155416615667) >> uint256(195 - k)).toInt256(); } } /// @dev Computes ln(x) in 1e18 fixed point. /// @dev Credit to Remco (https://github.com/recmo/experiment-solexp/blob/main/src/FixedPointMathLib.sol) /// @dev Reverts if x is negative or zero. /// @param x Fixed point number in 1e18 format. /// @return r Result of ln(x). function ln(int256 x) internal pure returns (int256 r) { unchecked { if (x <= 0) { revert IHyperdrive.LnInvalidInput(); } // We want to convert x from 10**18 fixed point to 2**96 fixed point. // We do this by multiplying by 2**96 / 10**18. But since // ln(x * C) = ln(x) + ln(C), we can simply do nothing here // and add ln(2**96 / 10**18) at the end. // This step inlines the `ilog2` call in Remco's implementation: // https://github.com/recmo/experiment-solexp/blob/bbc164fb5ec078cfccf3c71b521605106bfae00b/src/FixedPointMathLib.sol#L57-L68 // /// @solidity memory-safe-assembly assembly { r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x)) r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x)))) r := or(r, shl(5, lt(0xffffffff, shr(r, x)))) r := or(r, shl(4, lt(0xffff, shr(r, x)))) r := or(r, shl(3, lt(0xff, shr(r, x)))) r := or(r, shl(2, lt(0xf, shr(r, x)))) r := or(r, shl(1, lt(0x3, shr(r, x)))) r := or(r, lt(0x1, shr(r, x))) } // Reduce range of x to [1, 2) * 2**96 // ln(2^k * x) = k * ln(2) + ln(x) int256 k = r - 96; x <<= uint256(159 - k); x = (uint256(x) >> 159).toInt256(); // Evaluate using a (8, 8)-term rational approximation. // p is made monic, we will multiply by a scale factor later. int256 p = x + 3273285459638523848632254066296; p = ((p * x) >> 96) + 24828157081833163892658089445524; p = ((p * x) >> 96) + 43456485725739037958740375743393; p = ((p * x) >> 96) - 11111509109440967052023855526967; p = ((p * x) >> 96) - 45023709667254063763336534515857; p = ((p * x) >> 96) - 14706773417378608786704636184526; p = p * x - (795164235651350426258249787498 << 96); // We leave p in 2**192 basis so we don't need to scale it back up for the division. // q is monic by convention. int256 q = x + 5573035233440673466300451813936; q = ((q * x) >> 96) + 71694874799317883764090561454958; q = ((q * x) >> 96) + 283447036172924575727196451306956; q = ((q * x) >> 96) + 401686690394027663651624208769553; q = ((q * x) >> 96) + 204048457590392012362485061816622; q = ((q * x) >> 96) + 31853899698501571402653359427138; q = ((q * x) >> 96) + 909429971244387300277376558375; /// @solidity memory-safe-assembly assembly { // Div in assembly because solidity adds a zero check despite the unchecked. // The q polynomial is known not to have zeros in the domain. // No scaling required because p is already 2**96 too large. r := sdiv(p, q) } // r is in the range (0, 0.125) * 2**96 // Finalization, we need to: // * multiply by the scale factor s = 5.549… // * add ln(2**96 / 10**18) // * add k * ln(2) // * multiply by 10**18 / 2**96 = 5**18 >> 78 // mul s * 5e18 * 2**96, base is now 5**18 * 2**192 r *= 1677202110996718588342820967067443963516166; // add ln(2) * k * 5e18 * 2**192 r += 16597577552685614221487285958193947469193820559219878177908093499208371 * k; // add ln(2**96 / 10**18) * 5e18 * 2**192 r += 600920179829731861736702779321621459595472258049074101567377883020018308; // base conversion: mul 2**18 / 2**192 r >>= 174; } } /// @dev Updates a weighted average by adding or removing a weighted delta. /// @param _totalWeight The total weight before the update. /// @param _deltaWeight The weight of the new value. /// @param _average The weighted average before the update. /// @param _delta The new value. /// @return average The new weighted average. function updateWeightedAverage( uint256 _average, uint256 _totalWeight, uint256 _delta, uint256 _deltaWeight, bool _isAdding ) internal pure returns (uint256 average) { // If the delta weight is zero, the average does not change. if (_deltaWeight == 0) { return _average; } // If the delta weight should be added to the total weight, we compute // the weighted average as: // // average = (totalWeight * average + deltaWeight * delta) / // (totalWeight + deltaWeight) if (_isAdding) { // NOTE: Round down to underestimate the average. average = (_totalWeight.mulDown(_average) + _deltaWeight.mulDown(_delta)).divDown( _totalWeight + _deltaWeight ); // An important property that should always hold when we are adding // to the average is: // // min(_delta, _average) <= average <= max(_delta, _average) // // To ensure that this is always the case, we clamp the weighted // average to this range. We don't have to worry about the // case where average > _delta.max(average) because rounding down when // computing this average makes this case infeasible. uint256 minAverage = _delta.min(_average); if (average < minAverage) { average = minAverage; } } // If the delta weight should be subtracted from the total weight, we // compute the weighted average as: // // average = (totalWeight * average - deltaWeight * delta) / // (totalWeight - deltaWeight) else { if (_totalWeight == _deltaWeight) { return 0; } // NOTE: Round down to underestimate the average. average = (_totalWeight.mulDown(_average) - _deltaWeight.mulUp(_delta)).divDown( _totalWeight - _deltaWeight ); } } /// @dev Calculates the minimum of two values. /// @param a The first value. /// @param b The second value. /// @return The minimum of the two values. function min(uint256 a, uint256 b) internal pure returns (uint256) { return a > b ? b : a; } /// @dev Calculates the maximum of two values. /// @param a The first value. /// @param b The second value. /// @return The maximum of the two values. function max(uint256 a, uint256 b) internal pure returns (uint256) { return a > b ? a : b; } /// @dev Calculates the minimum of two values. /// @param a The first value. /// @param b The second value. /// @return The minimum of the two values. function min(int256 a, int256 b) internal pure returns (int256) { return a > b ? b : a; } /// @dev Calculates the maximum of two values. /// @param a The first value. /// @param b The second value. /// @return The maximum of the two values. function max(int256 a, int256 b) internal pure returns (int256) { return a > b ? a : b; } }
/// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; import { Errors } from "./Errors.sol"; import { FixedPointMath, ONE } from "./FixedPointMath.sol"; import { SafeCast } from "./SafeCast.sol"; import { YieldSpaceMath } from "./YieldSpaceMath.sol"; /// @author DELV /// @title Hyperdrive /// @notice Math for the Hyperdrive pricing model. /// @custom:disclaimer The language used in this code is for coding convenience /// only, and is not intended to, and does not, have any /// particular legal or regulatory significance. library HyperdriveMath { using FixedPointMath for uint256; using FixedPointMath for int256; using SafeCast for uint256; /// @dev Calculates the checkpoint time of a given timestamp. /// @param _timestamp The timestamp to use to calculate the checkpoint time. /// @param _checkpointDuration The checkpoint duration. /// @return The checkpoint time. function calculateCheckpointTime( uint256 _timestamp, uint256 _checkpointDuration ) internal pure returns (uint256) { return _timestamp - (_timestamp % _checkpointDuration); } /// @dev Calculates the time stretch parameter for the YieldSpace curve. /// This parameter modifies the curvature in order to support a larger /// or smaller range of APRs. The lower the time stretch, the flatter /// the curve will be and the narrower the range of feasible APRs. The /// higher the time stretch, the higher the curvature will be and the /// wider the range of feasible APRs. /// @param _apr The target APR to use when calculating the time stretch. /// @param _positionDuration The position duration in seconds. /// @return The time stretch parameter. function calculateTimeStretch( uint256 _apr, uint256 _positionDuration ) internal pure returns (uint256) { // Calculate the benchmark time stretch. This time stretch is tuned for // a position duration of 1 year. uint256 timeStretch = uint256(5.24592e18).divDown( uint256(0.04665e18).mulDown(_apr * 100) ); timeStretch = ONE.divDown(timeStretch); // We know that the following simultaneous equations hold: // // (1 + apr) * A ** timeStretch = 1 // // and // // (1 + apr * (positionDuration / 365 days)) * A ** targetTimeStretch = 1 // // where A is the reserve ratio. We can solve these equations for the // target time stretch as follows: // // targetTimeStretch = ( // ln(1 + apr * (positionDuration / 365 days)) / // ln(1 + apr) // ) * timeStretch // // NOTE: Round down so that the output is an underestimate. return ( uint256( (ONE + _apr.mulDivDown(_positionDuration, 365 days)) .toInt256() .ln() ).divDown(uint256((ONE + _apr).toInt256().ln())) ).mulDown(timeStretch); } /// @dev Calculates the APR implied by a price. /// @param _price The price to convert to an APR. /// @param _duration The term duration. /// @return The APR implied by the price. function calculateAPRFromPrice( uint256 _price, uint256 _duration ) internal pure returns (uint256) { // NOTE: Round down to underestimate the spot APR. return (ONE - _price).divDown( // NOTE: Round up since this is in the denominator. _price.mulDivUp(_duration, 365 days) ); } /// @dev Calculates the spot price of bonds in terms of base. This /// calculation underestimates the pool's spot price. /// @param _effectiveShareReserves The pool's effective share reserves. The /// effective share reserves are a modified version of the share /// reserves used when pricing trades. /// @param _bondReserves The pool's bond reserves. /// @param _initialVaultSharePrice The initial vault share price. /// @param _timeStretch The time stretch parameter. /// @return spotPrice The spot price of bonds in terms of base. function calculateSpotPrice( uint256 _effectiveShareReserves, uint256 _bondReserves, uint256 _initialVaultSharePrice, uint256 _timeStretch ) internal pure returns (uint256 spotPrice) { // NOTE: Round down to underestimate the spot price. // // p = (y / (mu * (z - zeta))) ** -t_s // = ((mu * (z - zeta)) / y) ** t_s spotPrice = _initialVaultSharePrice .mulDivDown(_effectiveShareReserves, _bondReserves) .pow(_timeStretch); } /// @dev Calculates the spot APR of the pool. This calculation /// underestimates the pool's spot APR. /// @param _effectiveShareReserves The pool's effective share reserves. The /// effective share reserves are a modified version of the share /// reserves used when pricing trades. /// @param _bondReserves The pool's bond reserves. /// @param _initialVaultSharePrice The pool's initial vault share price. /// @param _positionDuration The amount of time until maturity in seconds. /// @param _timeStretch The time stretch parameter. /// @return apr The pool's spot APR. function calculateSpotAPR( uint256 _effectiveShareReserves, uint256 _bondReserves, uint256 _initialVaultSharePrice, uint256 _positionDuration, uint256 _timeStretch ) internal pure returns (uint256 apr) { // NOTE: Round down to underestimate the spot APR. // // We are interested calculating the fixed APR for the pool. The // annualized rate is given by the following formula: // // r = (1 - p) / (p * t) // // where t = _positionDuration / 365. uint256 spotPrice = calculateSpotPrice( _effectiveShareReserves, _bondReserves, _initialVaultSharePrice, _timeStretch ); return calculateAPRFromPrice(spotPrice, _positionDuration); } /// @dev Calculates the effective share reserves. The effective share /// reserves are the share reserves minus the share adjustment or /// z - zeta. We use the effective share reserves as the z-parameter /// to the YieldSpace pricing model. The share adjustment is used to /// hold the pricing mechanism invariant under the flat component of /// flat+curve trades. /// @param _shareReserves The pool's share reserves. /// @param _shareAdjustment The pool's share adjustment. /// @return effectiveShareReserves The effective share reserves. function calculateEffectiveShareReserves( uint256 _shareReserves, int256 _shareAdjustment ) internal pure returns (uint256 effectiveShareReserves) { bool success; (effectiveShareReserves, success) = calculateEffectiveShareReservesSafe( _shareReserves, _shareAdjustment ); if (!success) { Errors.throwInsufficientLiquidityError(); } } /// @dev Calculates the effective share reserves. The effective share /// reserves are the share reserves minus the share adjustment or /// z - zeta. We use the effective share reserves as the z-parameter /// to the YieldSpace pricing model. The share adjustment is used to /// hold the pricing mechanism invariant under the flat component of /// flat+curve trades. /// @param _shareReserves The pool's share reserves. /// @param _shareAdjustment The pool's share adjustment. /// @return The effective share reserves. /// @return A flag indicating if the calculation succeeded. function calculateEffectiveShareReservesSafe( uint256 _shareReserves, int256 _shareAdjustment ) internal pure returns (uint256, bool) { int256 effectiveShareReserves = _shareReserves.toInt256() - _shareAdjustment; if (effectiveShareReserves < 0) { return (0, false); } return (uint256(effectiveShareReserves), true); } /// @dev Calculates the proceeds in shares of closing a short position. This /// takes into account the trading profits, the interest that was /// earned by the short, the flat fee the short pays, and the amount of /// margin that was released by closing the short. The math for the /// short's proceeds in base is given by: /// /// proceeds = (1 + flat_fee) * dy - c * dz + (c1 - c0) * (dy / c0) /// = (1 + flat_fee) * dy - c * dz + (c1 / c0) * dy - dy /// = (c1 / c0 + flat_fee) * dy - c * dz /// /// We convert the proceeds to shares by dividing by the current vault /// share price. In the event that the interest is negative and /// outweighs the trading profits and margin released, the short's /// proceeds are marked to zero. /// /// This variant of the calculation overestimates the short proceeds. /// @param _bondAmount The amount of bonds underlying the closed short. /// @param _shareAmount The amount of shares that it costs to close the /// short. /// @param _openVaultSharePrice The vault share price at the short's open. /// @param _closeVaultSharePrice The vault share price at the short's close. /// @param _vaultSharePrice The current vault share price. /// @param _flatFee The flat fee currently within the pool /// @return shareProceeds The short proceeds in shares. function calculateShortProceedsUp( uint256 _bondAmount, uint256 _shareAmount, uint256 _openVaultSharePrice, uint256 _closeVaultSharePrice, uint256 _vaultSharePrice, uint256 _flatFee ) internal pure returns (uint256 shareProceeds) { // NOTE: Round up to overestimate the short proceeds. // // The total value is the amount of shares that underlies the bonds that // were shorted. The bonds start by being backed 1:1 with base, and the // total value takes into account all of the interest that has accrued // since the short was opened. // // total_value = (c1 / (c0 * c)) * dy uint256 totalValue = _bondAmount .mulDivUp(_closeVaultSharePrice, _openVaultSharePrice) .divUp(_vaultSharePrice); // NOTE: Round up to overestimate the short proceeds. // // We increase the total value by the flat fee amount, because it is // included in the total amount of capital underlying the short. totalValue += _bondAmount.mulDivUp(_flatFee, _vaultSharePrice); // If the interest is more negative than the trading profits and margin // released, then the short proceeds are marked to zero. Otherwise, we // calculate the proceeds as the sum of the trading proceeds, the // interest proceeds, and the margin released. if (totalValue > _shareAmount) { // proceeds = (c1 / (c0 * c)) * dy - dz unchecked { shareProceeds = totalValue - _shareAmount; } } return shareProceeds; } /// @dev Calculates the proceeds in shares of closing a short position. This /// takes into account the trading profits, the interest that was /// earned by the short, the flat fee the short pays, and the amount of /// margin that was released by closing the short. The math for the /// short's proceeds in base is given by: /// /// proceeds = (1 + flat_fee) * dy - c * dz + (c1 - c0) * (dy / c0) /// = (1 + flat_fee) * dy - c * dz + (c1 / c0) * dy - dy /// = (c1 / c0 + flat_fee) * dy - c * dz /// /// We convert the proceeds to shares by dividing by the current vault /// share price. In the event that the interest is negative and /// outweighs the trading profits and margin released, the short's /// proceeds are marked to zero. /// /// This variant of the calculation underestimates the short proceeds. /// @param _bondAmount The amount of bonds underlying the closed short. /// @param _shareAmount The amount of shares that it costs to close the /// short. /// @param _openVaultSharePrice The vault share price at the short's open. /// @param _closeVaultSharePrice The vault share price at the short's close. /// @param _vaultSharePrice The current vault share price. /// @param _flatFee The flat fee currently within the pool /// @return shareProceeds The short proceeds in shares. function calculateShortProceedsDown( uint256 _bondAmount, uint256 _shareAmount, uint256 _openVaultSharePrice, uint256 _closeVaultSharePrice, uint256 _vaultSharePrice, uint256 _flatFee ) internal pure returns (uint256 shareProceeds) { // NOTE: Round down to underestimate the short proceeds. // // The total value is the amount of shares that underlies the bonds that // were shorted. The bonds start by being backed 1:1 with base, and the // total value takes into account all of the interest that has accrued // since the short was opened. // // total_value = (c1 / (c0 * c)) * dy uint256 totalValue = _bondAmount .mulDivDown(_closeVaultSharePrice, _openVaultSharePrice) .divDown(_vaultSharePrice); // NOTE: Round down to underestimate the short proceeds. // // We increase the total value by the flat fee amount, because it is // included in the total amount of capital underlying the short. totalValue += _bondAmount.mulDivDown(_flatFee, _vaultSharePrice); // If the interest is more negative than the trading profits and margin // released, then the short proceeds are marked to zero. Otherwise, we // calculate the proceeds as the sum of the trading proceeds, the // interest proceeds, and the margin released. if (totalValue > _shareAmount) { // proceeds = (c1 / (c0 * c)) * dy - dz unchecked { shareProceeds = totalValue - _shareAmount; } } return shareProceeds; } /// @dev Since traders pay a curve fee when they open longs on Hyperdrive, /// it is possible for traders to receive a negative interest rate even /// if curve's spot price is less than or equal to 1. /// /// Given the curve fee `phi_c` and the starting spot price `p_0`, the /// maximum spot price is given by: /// /// p_max = (1 - phi_f) / (1 + phi_c * (1 / p_0 - 1) * (1 - phi_f)) /// /// We underestimate the maximum spot price to be conservative. /// @param _startingSpotPrice The spot price at the start of the trade. /// @param _curveFee The curve fee. /// @param _flatFee The flat fee. /// @return The maximum spot price. function calculateOpenLongMaxSpotPrice( uint256 _startingSpotPrice, uint256 _curveFee, uint256 _flatFee ) internal pure returns (uint256) { // NOTE: Round down to underestimate the maximum spot price. return (ONE - _flatFee).divDown( // NOTE: Round up since this is in the denominator. ONE + _curveFee.mulUp(ONE.divUp(_startingSpotPrice) - ONE).mulUp( ONE - _flatFee ) ); } /// @dev Since traders pay a curve fee when they close shorts on Hyperdrive, /// it is possible for traders to receive a negative interest rate even /// if curve's spot price is less than or equal to 1. /// /// Given the curve fee `phi_c` and the starting spot price `p_0`, the /// maximum spot price is given by: /// /// p_max = 1 - phi_c * (1 - p_0) /// /// We underestimate the maximum spot price to be conservative. /// @param _startingSpotPrice The spot price at the start of the trade. /// @param _curveFee The curve fee. /// @return The maximum spot price. function calculateCloseShortMaxSpotPrice( uint256 _startingSpotPrice, uint256 _curveFee ) internal pure returns (uint256) { // Round the rhs down to underestimate the maximum spot price. return ONE - _curveFee.mulUp(ONE - _startingSpotPrice); } /// @dev Calculates the number of bonds a user will receive when opening a /// long position. /// @param _effectiveShareReserves The pool's effective share reserves. The /// effective share reserves are a modified version of the share /// reserves used when pricing trades. /// @param _bondReserves The pool's bond reserves. /// @param _shareAmount The amount of shares the user is depositing. /// @param _timeStretch The time stretch parameter. /// @param _vaultSharePrice The vault share price. /// @param _initialVaultSharePrice The initial vault share price. /// @return bondReservesDelta The bonds paid by the reserves in the trade. function calculateOpenLong( uint256 _effectiveShareReserves, uint256 _bondReserves, uint256 _shareAmount, uint256 _timeStretch, uint256 _vaultSharePrice, uint256 _initialVaultSharePrice ) internal pure returns (uint256) { // NOTE: We underestimate the trader's bond proceeds to avoid sandwich // attacks. return YieldSpaceMath.calculateBondsOutGivenSharesInDown( _effectiveShareReserves, _bondReserves, _shareAmount, // NOTE: Since the bonds traded on the curve are newly minted, // we use a time remaining of 1. This means that we can use // `_timeStretch = t * _timeStretch`. ONE - _timeStretch, _vaultSharePrice, _initialVaultSharePrice ); } /// @dev Calculates the amount of shares a user will receive when closing a /// long position. /// @param _effectiveShareReserves The pool's effective share reserves. The /// effective share reserves are a modified version of the share /// reserves used when pricing trades. /// @param _bondReserves The pool's bond reserves. /// @param _amountIn The amount of bonds the user is closing. /// @param _normalizedTimeRemaining The normalized time remaining of the /// position. /// @param _timeStretch The time stretch parameter. /// @param _vaultSharePrice The vault share price. /// @param _initialVaultSharePrice The vault share price when the pool was /// deployed. /// @return shareCurveDelta The shares paid by the reserves in the trade. /// @return bondCurveDelta The bonds paid to the reserves in the trade. /// @return shareProceeds The shares that the user will receive. function calculateCloseLong( uint256 _effectiveShareReserves, uint256 _bondReserves, uint256 _amountIn, uint256 _normalizedTimeRemaining, uint256 _timeStretch, uint256 _vaultSharePrice, uint256 _initialVaultSharePrice ) internal pure returns ( uint256 shareCurveDelta, uint256 bondCurveDelta, uint256 shareProceeds ) { // NOTE: We underestimate the trader's share proceeds to avoid sandwich // attacks. // // We consider `(1 - timeRemaining) * amountIn` of the bonds to be fully // matured and timeRemaining * amountIn of the bonds to be newly // minted. The fully matured bonds are redeemed one-to-one to base // (our result is given in shares, so we divide the one-to-one // redemption by the vault share price) and the newly minted bonds are // traded on a YieldSpace curve configured to `timeRemaining = 1`. shareProceeds = _amountIn.mulDivDown( ONE - _normalizedTimeRemaining, _vaultSharePrice ); if (_normalizedTimeRemaining > 0) { // NOTE: Round the `bondCurveDelta` down to underestimate the share // proceeds. // // Calculate the curved part of the trade. bondCurveDelta = _amountIn.mulDown(_normalizedTimeRemaining); // NOTE: Round the `shareCurveDelta` down to underestimate the // share proceeds. shareCurveDelta = YieldSpaceMath.calculateSharesOutGivenBondsInDown( _effectiveShareReserves, _bondReserves, bondCurveDelta, // NOTE: Since the bonds traded on the curve are newly minted, // we use a time remaining of 1. This means that we can use // `_timeStretch = t * _timeStretch`. ONE - _timeStretch, _vaultSharePrice, _initialVaultSharePrice ); shareProceeds += shareCurveDelta; } } /// @dev Calculates the amount of shares that will be received given a /// specified amount of bonds. /// @param _effectiveShareReserves The pool's effective share reserves. The /// effective share reserves are a modified version of the share /// reserves used when pricing trades. /// @param _bondReserves The pool's bonds reserves. /// @param _amountIn The amount of bonds the user is providing. /// @param _timeStretch The time stretch parameter. /// @param _vaultSharePrice The vault share price. /// @param _initialVaultSharePrice The initial vault share price. /// @return The shares paid by the reserves in the trade. function calculateOpenShort( uint256 _effectiveShareReserves, uint256 _bondReserves, uint256 _amountIn, uint256 _timeStretch, uint256 _vaultSharePrice, uint256 _initialVaultSharePrice ) internal pure returns (uint256) { // NOTE: We underestimate the LP's share payment to avoid sandwiches. return YieldSpaceMath.calculateSharesOutGivenBondsInDown( _effectiveShareReserves, _bondReserves, _amountIn, // NOTE: Since the bonds traded on the curve are newly minted, // we use a time remaining of 1. This means that we can use // `_timeStretch = t * _timeStretch`. ONE - _timeStretch, _vaultSharePrice, _initialVaultSharePrice ); } /// @dev Calculates the amount of base that a user will receive when closing /// a short position. /// @param _effectiveShareReserves The pool's effective share reserves. The /// effective share reserves are a modified version of the share /// reserves used when pricing trades. /// @param _bondReserves The pool's bonds reserves. /// @param _amountOut The amount of the asset that is received. /// @param _normalizedTimeRemaining The amount of time remaining until /// maturity in seconds. /// @param _timeStretch The time stretch parameter. /// @param _vaultSharePrice The vault share price. /// @param _initialVaultSharePrice The initial vault share price. /// @return shareCurveDelta The shares paid to the reserves in the trade. /// @return bondCurveDelta The bonds paid by the reserves in the trade. /// @return sharePayment The shares that the user must pay. function calculateCloseShort( uint256 _effectiveShareReserves, uint256 _bondReserves, uint256 _amountOut, uint256 _normalizedTimeRemaining, uint256 _timeStretch, uint256 _vaultSharePrice, uint256 _initialVaultSharePrice ) internal pure returns ( uint256 shareCurveDelta, uint256 bondCurveDelta, uint256 sharePayment ) { // NOTE: We overestimate the trader's share payment to avoid sandwiches. // // Since we are buying bonds, it's possible that `timeRemaining < 1`. // We consider `(1 - timeRemaining) * amountOut` of the bonds being // purchased to be fully matured and `timeRemaining * amountOut of the // bonds to be newly minted. The fully matured bonds are redeemed // one-to-one to base (our result is given in shares, so we divide // the one-to-one redemption by the vault share price) and the newly // minted bonds are traded on a YieldSpace curve configured to // timeRemaining = 1. sharePayment = _amountOut.mulDivUp( ONE - _normalizedTimeRemaining, _vaultSharePrice ); if (_normalizedTimeRemaining > 0) { // NOTE: Round the `bondCurveDelta` up to overestimate the share // payment. bondCurveDelta = _amountOut.mulUp(_normalizedTimeRemaining); // NOTE: Round the `shareCurveDelta` up to overestimate the share // payment. shareCurveDelta = YieldSpaceMath.calculateSharesInGivenBondsOutUp( _effectiveShareReserves, _bondReserves, bondCurveDelta, // NOTE: Since the bonds traded on the curve are newly minted, // we use a time remaining of 1. This means that we can use // `_timeStretch = t * _timeStretch`. ONE - _timeStretch, _vaultSharePrice, _initialVaultSharePrice ); sharePayment += shareCurveDelta; } } /// @dev If negative interest accrued over the term, we scale the share /// proceeds by the negative interest amount. Shorts should be /// responsible for negative interest, but negative interest can exceed /// the margin that shorts provide. This leaves us with no choice but /// to attribute the negative interest to longs. Along with scaling the /// share proceeds, we also scale the fee amounts. /// /// In order for our AMM invariant to be maintained, the effective /// share reserves need to be adjusted by the same amount as the share /// reserves delta calculated with YieldSpace including fees. We reduce /// the share reserves by `min(c_1 / c_0, 1) * shareReservesDelta` and /// the share adjustment by the `shareAdjustmentDelta`. We can solve /// these equations simultaneously to find the share adjustment delta /// as: /// /// shareAdjustmentDelta = min(c_1 / c_0, 1) * sharePayment - /// shareReservesDelta /// /// We underestimate the share proceeds to avoid sandwiches, and we /// round the share reserves delta and share adjustment in the same /// direction for consistency. /// @param _shareProceeds The proceeds in shares from the trade. /// @param _shareReservesDelta The change in share reserves from the trade. /// @param _shareCurveDelta The curve portion of the change in share reserves. /// @param _totalGovernanceFee The total governance fee. /// @param _openVaultSharePrice The vault share price at the beginning of /// the term. /// @param _closeVaultSharePrice The vault share price at the end of the term. /// @param _isLong A flag indicating whether or not the trade is a long. /// @return The adjusted share proceeds. /// @return The adjusted share reserves delta. /// @return The adjusted share close proceeds. /// @return The share adjustment delta. /// @return The adjusted total governance fee. function calculateNegativeInterestOnClose( uint256 _shareProceeds, uint256 _shareReservesDelta, uint256 _shareCurveDelta, uint256 _totalGovernanceFee, uint256 _openVaultSharePrice, uint256 _closeVaultSharePrice, bool _isLong ) internal pure returns (uint256, uint256, uint256, int256, uint256) { // The share reserves delta, share curve delta, and total governance fee // need to be scaled down in proportion to the negative interest. This // results in the pool receiving a lower payment, which reflects the // fact that negative interest is attributed to longs. // // In order for our AMM invariant to be maintained, the effective share // reserves need to be adjusted by the same amount as the share reserves // delta calculated with YieldSpace including fees. We increase the // share reserves by `min(c_1 / c_0, 1) * shareReservesDelta` and the // share adjustment by the `shareAdjustmentDelta`. We can solve these // equations simultaneously to find the share adjustment delta as: // // shareAdjustmentDelta = min(c_1 / c_0, 1) * shareReservesDelta - // shareCurveDelta int256 shareAdjustmentDelta; if (_closeVaultSharePrice < _openVaultSharePrice) { // NOTE: Round down to underestimate the share proceeds. // // We only need to scale the proceeds in the case that we're closing // a long since `calculateShortProceeds` accounts for negative // interest. if (_isLong) { _shareProceeds = _shareProceeds.mulDivDown( _closeVaultSharePrice, _openVaultSharePrice ); } // NOTE: Round down to underestimate the quantities. // // Scale the other values. _shareReservesDelta = _shareReservesDelta.mulDivDown( _closeVaultSharePrice, _openVaultSharePrice ); // NOTE: Using unscaled `shareCurveDelta`. shareAdjustmentDelta = _shareReservesDelta.toInt256() - _shareCurveDelta.toInt256(); _shareCurveDelta = _shareCurveDelta.mulDivDown( _closeVaultSharePrice, _openVaultSharePrice ); _totalGovernanceFee = _totalGovernanceFee.mulDivDown( _closeVaultSharePrice, _openVaultSharePrice ); } else { shareAdjustmentDelta = _shareReservesDelta.toInt256() - _shareCurveDelta.toInt256(); } return ( _shareProceeds, _shareReservesDelta, _shareCurveDelta, shareAdjustmentDelta, _totalGovernanceFee ); } }
// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; import { SignedMath } from "openzeppelin/utils/math/SignedMath.sol"; import { IHyperdrive } from "../interfaces/IHyperdrive.sol"; import { FixedPointMath, ONE } from "./FixedPointMath.sol"; import { HyperdriveMath } from "./HyperdriveMath.sol"; import { SafeCast } from "./SafeCast.sol"; import { YieldSpaceMath } from "./YieldSpaceMath.sol"; /// @author DELV /// @title LPMath /// @notice Math for the Hyperdrive LP system. /// @custom:disclaimer The language used in this code is for coding convenience /// only, and is not intended to, and does not, have any /// particular legal or regulatory significance. library LPMath { using FixedPointMath for *; using SafeCast for uint256; using SignedMath for int256; /// @dev The maximum number of iterations for the share proceeds calculation. uint256 internal constant SHARE_PROCEEDS_MAX_ITERATIONS = 4; /// @dev The minimum tolerance for the share proceeds calculation to /// short-circuit. uint256 internal constant SHARE_PROCEEDS_SHORT_CIRCUIT_TOLERANCE = 1e9; /// @dev The minimum tolerance for the share proceeds calculation to /// succeed. uint256 internal constant SHARE_PROCEEDS_TOLERANCE = 1e14; struct PresentValueParams { uint256 shareReserves; int256 shareAdjustment; uint256 bondReserves; uint256 vaultSharePrice; uint256 initialVaultSharePrice; uint256 minimumShareReserves; uint256 minimumTransactionAmount; uint256 timeStretch; uint256 longsOutstanding; uint256 longAverageTimeRemaining; uint256 shortsOutstanding; uint256 shortAverageTimeRemaining; } struct DistributeExcessIdleParams { PresentValueParams presentValueParams; uint256 startingPresentValue; uint256 activeLpTotalSupply; uint256 withdrawalSharesTotalSupply; uint256 idle; int256 netCurveTrade; uint256 originalShareReserves; int256 originalShareAdjustment; uint256 originalBondReserves; } /// @dev Calculates the initial reserves. We solve for the initial reserves /// by solving the following equations simultaneously: /// /// (1) c * z = c * z_e + p_target * y /// /// (2) p_target = ((mu * z_e) / y) ** t_s /// /// where p_target is the target spot price implied by the target spot /// rate. /// @param _shareAmount The amount of shares used to initialize the pool. /// @param _vaultSharePrice The vault share price. /// @param _initialVaultSharePrice The initial vault share price. /// @param _targetApr The target rate. /// @param _positionDuration The position duration. /// @param _timeStretch The time stretch. /// @return shareReserves The initial share reserves. /// @return shareAdjustment The initial share adjustment. /// @return bondReserves The initial bond reserves. function calculateInitialReserves( uint256 _shareAmount, uint256 _vaultSharePrice, uint256 _initialVaultSharePrice, uint256 _targetApr, uint256 _positionDuration, uint256 _timeStretch ) external pure returns ( uint256 shareReserves, int256 shareAdjustment, uint256 bondReserves ) { // NOTE: Round down to underestimate the initial bond reserves. // // Normalize the time to maturity to fractions of a year since the // provided rate is an APR. uint256 t = _positionDuration.divDown(365 days); // NOTE: Round up to underestimate the initial bond reserves. // // Calculate the target price implied by the target rate. uint256 targetPrice = ONE.divUp(ONE + _targetApr.mulDown(t)); // The share reserves is just the share amount since we are initializing // the pool. shareReserves = _shareAmount; // NOTE: Round down to underestimate the initial bond reserves. // // Calculate the initial bond reserves. This is given by: // // y = (mu * c * z) / (c * p_target ** (1 / t_s) + mu * p_target) bondReserves = _initialVaultSharePrice.mulDivDown( _vaultSharePrice.mulDown(shareReserves), _vaultSharePrice.mulUp(targetPrice.pow(ONE.divDown(_timeStretch))) + _initialVaultSharePrice.mulUp(targetPrice) ); // NOTE: Round down to underestimate the initial share adjustment. // // Calculate the initial share adjustment. This is given by: // // zeta = (p_target * y) / c shareAdjustment = int256( bondReserves.mulDivDown(targetPrice, _vaultSharePrice) ); } /// @dev Calculates the global long exposure after an update is made to /// a checkpoint exposure. /// @param _longExposure The global long exposure. /// @param _before The checkpoint long exposure before the update. /// @param _after The checkpoint long exposure after the update. /// @return The updated global long exposure. function calculateLongExposure( uint256 _longExposure, int256 _before, int256 _after ) internal pure returns (uint256) { // The global long exposure is the sum of the non-netted longs in each // checkpoint. To update this value, we subtract the current value // (`_before.max(0)`) and add the new value (`_after.max(0)`). int256 delta = FixedPointMath.max(_after, 0) - FixedPointMath.max(_before, 0); if (delta > 0) { _longExposure += uint256(delta); } else if (delta < 0) { _longExposure -= uint256(-delta); } return _longExposure; } /// @dev Calculates the new share reserves, share adjustment, and bond /// reserves after liquidity is added or removed from the pool. This /// update is made in such a way that the pool's spot price remains /// constant. /// @param _shareReserves The current share reserves. /// @param _shareAdjustment The current share adjustment. /// @param _bondReserves The current bond reserves. /// @param _minimumShareReserves The minimum share reserves. /// @param _shareReservesDelta The change in share reserves. /// @return shareReserves The updated share reserves. /// @return shareAdjustment The updated share adjustment. /// @return bondReserves The updated bond reserves. /// @return A flag indicating if the calculation succeeded. function calculateUpdateLiquiditySafe( uint256 _shareReserves, int256 _shareAdjustment, uint256 _bondReserves, uint256 _minimumShareReserves, int256 _shareReservesDelta ) public pure returns ( uint256 shareReserves, int256 shareAdjustment, uint256 bondReserves, bool ) { // If the share reserves delta is zero, we can return early since no // action is needed. if (_shareReservesDelta == 0) { return (_shareReserves, _shareAdjustment, _bondReserves, true); } // Update the share reserves by applying the share reserves delta. We // ensure that our minimum share reserves invariant is still maintained. int256 shareReserves_ = _shareReserves.toInt256() + _shareReservesDelta; if (shareReserves_ < _minimumShareReserves.toInt256()) { return (0, 0, 0, false); } shareReserves = uint256(shareReserves_); // Update the share adjustment by holding the ratio of share reserves // to share adjustment proportional. In general, our pricing model cannot // support negative values for the z coordinate, so this is important as // it ensures that if z - zeta starts as a positive value, it ends as a // positive value. With this in mind, we update the share adjustment as: // // zeta_old / z_old = zeta_new / z_new // => // zeta_new = zeta_old * (z_new / z_old) if (_shareAdjustment >= 0) { // NOTE: Rounding down to have a larger effective share reserves. shareAdjustment = shareReserves .mulDivDown(uint256(_shareAdjustment), _shareReserves) .toInt256(); } else { // NOTE: Rounding up to have a larger effective share reserves. shareAdjustment = -shareReserves .mulDivUp(uint256(-_shareAdjustment), _shareReserves) .toInt256(); } // NOTE: Rounding down to avoid introducing dust into the computation. // // The liquidity update should hold the spot price invariant. The spot // price of base in terms of bonds is given by: // // p = (mu * (z - zeta) / y) ** tau // // This formula implies that holding the ratio of share reserves to bond // reserves constant will hold the spot price constant. This allows us // to calculate the updated bond reserves as: // // (z_old - zeta_old) / y_old = (z_new - zeta_new) / y_new // => // y_new = (z_new - zeta_new) * (y_old / (z_old - zeta_old)) (uint256 oldEffectiveShareReserves, bool success) = HyperdriveMath .calculateEffectiveShareReservesSafe( _shareReserves, _shareAdjustment ); if (!success) { return (0, 0, 0, false); } uint256 effectiveShareReserves; (effectiveShareReserves, success) = HyperdriveMath .calculateEffectiveShareReservesSafe( shareReserves, shareAdjustment ); if (!success) { return (0, 0, 0, false); } bondReserves = _bondReserves.mulDivDown( effectiveShareReserves, oldEffectiveShareReserves ); return (shareReserves, shareAdjustment, bondReserves, true); } /// @dev Calculates the present value LPs capital in the pool and reverts /// if the value is negative. This calculation underestimates the /// present value to avoid paying out more than the pool can afford. /// @param _params The parameters for the present value calculation. /// @return The present value of the pool. function calculatePresentValue( PresentValueParams memory _params ) public pure returns (uint256) { (uint256 presentValue, bool success) = calculatePresentValueSafe( _params ); if (!success) { revert IHyperdrive.InvalidPresentValue(); } return presentValue; } /// @dev Calculates the present value LPs capital in the pool and returns /// a flag indicating whether the calculation succeeded or failed. /// For the most part, this calculation underestimates the present /// value to avoid paying out more than the pool can afford; however, /// it adheres faithfully to the rounding utilized when positions are /// closed to accurately simulate the impact of closing the net curve /// position. /// @param _params The parameters for the present value calculation. /// @return The present value of the pool. /// @return A flag indicating whether the calculation succeeded or failed. function calculatePresentValueSafe( PresentValueParams memory _params ) public pure returns (uint256, bool) { // We calculate the LP present value by simulating the closing of all // of the outstanding long and short positions and applying this impact // on the share reserves. The present value is the share reserves after // the impact of the trades minus the minimum share reserves: // // PV = z + net_c + net_f - z_min int256 presentValue; { (int256 netCurveTrade, bool success) = calculateNetCurveTradeSafe( _params ); if (!success) { return (0, false); } presentValue = _params.shareReserves.toInt256() + netCurveTrade + calculateNetFlatTrade(_params) - _params.minimumShareReserves.toInt256(); } // If the present value is negative, return a failure flag indicating // the failure. if (presentValue < 0) { return (0, false); } return (uint256(presentValue), true); } /// @dev Calculates the result of closing the net curve position. /// @param _params The parameters for the present value calculation. /// @return The impact of closing the net curve position on the share /// reserves. /// @return A flag indicating whether the calculation succeeded or failed. function calculateNetCurveTradeSafe( PresentValueParams memory _params ) internal pure returns (int256, bool) { // NOTE: To underestimate the impact of closing the net curve position, // we round up the long side of the net curve position (since this // results in a larger value removed from the share reserves) and round // down the short side of the net curve position (since this results in // a smaller value added to the share reserves). // // The net curve position is the net of the longs and shorts that are // currently tradeable on the curve. Given the amount of outstanding // longs `y_l` and shorts `y_s` as well as the average time remaining // of outstanding longs `t_l` and shorts `t_s`, we can // compute the net curve position as: // // netCurveTrade = y_l * t_l - y_s * t_s. int256 netCurvePosition = _params .longsOutstanding .mulUp(_params.longAverageTimeRemaining) .toInt256() - _params .shortsOutstanding .mulDown(_params.shortAverageTimeRemaining) .toInt256(); (uint256 effectiveShareReserves, bool success) = HyperdriveMath .calculateEffectiveShareReservesSafe( _params.shareReserves, _params.shareAdjustment ); if (!success) { // NOTE: Return 0 to indicate that the net curve trade couldn't be // computed. return (0, false); } // If the net curve position is positive, then the pool is net long. // Closing the net curve position results in the longs being paid out // from the share reserves, so we negate the result. if (netCurvePosition > 0) { uint256 netCurvePosition_ = uint256(netCurvePosition); // Calculate the maximum amount of bonds that can be sold on // YieldSpace. If this calculation fails, then we return a failure // flag. uint256 maxCurveTrade; (maxCurveTrade, success) = YieldSpaceMath .calculateMaxSellBondsInSafe( _params.shareReserves, _params.shareAdjustment, _params.bondReserves, _params.minimumShareReserves, ONE - _params.timeStretch, _params.vaultSharePrice, _params.initialVaultSharePrice ); if (!success) { // NOTE: Return 0 to indicate that the net curve trade couldn't // be computed. return (0, false); } // If the max curve trade is greater than the net curve position, // then we can close the entire net curve position. if (maxCurveTrade >= netCurvePosition_) { // NOTE: We round in the same direction as when closing longs // to accurately estimate the impact of closing the net curve // position. // // Calculate the net curve trade. uint256 netCurveTrade; (netCurveTrade, success) = YieldSpaceMath .calculateSharesOutGivenBondsInDownSafe( effectiveShareReserves, _params.bondReserves, netCurvePosition_, ONE - _params.timeStretch, _params.vaultSharePrice, _params.initialVaultSharePrice ); // If the net curve position is smaller than the minimum // transaction amount and the trade fails, we mark it to 0. This // prevents liveness problems when the net curve position is // very small. if ( !success && netCurvePosition_ < _params.minimumTransactionAmount ) { return (0, true); } // Otherwise, we return a failure flag. else if (!success) { return (0, false); } return (-netCurveTrade.toInt256(), true); } // Otherwise, we can only close part of the net curve position. // Since the spot price is approximately zero after closing the // entire net curve position, we mark any remaining bonds to zero. else { // If the share adjustment is greater than or equal to zero, // then the effective share reserves are less than or equal to // the share reserves. In this case, the maximum amount of // shares that can be removed from the share reserves is // `effectiveShareReserves - minimumShareReserves`. if (_params.shareAdjustment >= 0) { return ( -(effectiveShareReserves - _params.minimumShareReserves) .toInt256(), true ); } // Otherwise, the effective share reserves are greater than the // share reserves. In this case, the maximum amount of shares // that can be removed from the share reserves is // `shareReserves - minimumShareReserves`. else { return ( -(_params.shareReserves - _params.minimumShareReserves) .toInt256(), true ); } } } // If the net curve position is negative, then the pool is net short. else if (netCurvePosition < 0) { uint256 netCurvePosition_ = uint256(-netCurvePosition); // Calculate the maximum amount of bonds that can be bought on // YieldSpace. uint256 maxCurveTrade; (maxCurveTrade, success) = YieldSpaceMath .calculateMaxBuyBondsOutSafe( effectiveShareReserves, _params.bondReserves, ONE - _params.timeStretch, _params.vaultSharePrice, _params.initialVaultSharePrice ); if (!success) { return (0, false); } // If the max curve trade is greater than the net curve position, // then we can close the entire net curve position. if (maxCurveTrade >= netCurvePosition_) { // NOTE: We round in the same direction as when closing shorts // to accurately estimate the impact of closing the net curve // position. // // Calculate the net curve trade. uint256 netCurveTrade; (netCurveTrade, success) = YieldSpaceMath .calculateSharesInGivenBondsOutUpSafe( effectiveShareReserves, _params.bondReserves, netCurvePosition_, ONE - _params.timeStretch, _params.vaultSharePrice, _params.initialVaultSharePrice ); // If the net curve position is smaller than the minimum // transaction amount and the trade fails, we mark it to 0. This // prevents liveness problems when the net curve position is // very small. if ( !success && netCurvePosition_ < _params.minimumTransactionAmount ) { return (0, true); } // Otherwise, we return a failure flag. else if (!success) { return (0, false); } return (netCurveTrade.toInt256(), true); } // Otherwise, we can only close part of the net curve position. // Since the spot price is equal to one after closing the entire net // curve position, we mark any remaining bonds to one. else { // Calculate the max share payment. uint256 maxSharePayment; (maxSharePayment, success) = YieldSpaceMath .calculateMaxBuySharesInSafe( effectiveShareReserves, _params.bondReserves, ONE - _params.timeStretch, _params.vaultSharePrice, _params.initialVaultSharePrice ); if (!success) { return (0, false); } return ( // NOTE: We round the difference down to underestimate the // impact of closing the net curve position. (maxSharePayment + (netCurvePosition_ - maxCurveTrade).divDown( _params.vaultSharePrice )).toInt256(), true ); } } return (0, true); } /// @dev Calculates the result of closing the net flat position. /// @param _params The parameters for the present value calculation. /// @return The impact of closing the net flat position on the share /// reserves. function calculateNetFlatTrade( PresentValueParams memory _params ) internal pure returns (int256) { // NOTE: In order to underestimate the impact of closing all of the // flat trades, we round the impact of closing the shorts down and round // the impact of closing the longs up. // // The net curve position is the net of the component of longs and // shorts that have matured. Given the amount of outstanding longs `y_l` // and shorts `y_s` as well as the average time remaining of outstanding // longs `t_l` and shorts `t_s`, we can compute the net flat trade as: // // netFlatTrade = y_s * (1 - t_s) - y_l * (1 - t_l). return ( _params.shortsOutstanding.mulDivDown( ONE - _params.shortAverageTimeRemaining, _params.vaultSharePrice ) ).toInt256() - ( _params.longsOutstanding.mulDivUp( ONE - _params.longAverageTimeRemaining, _params.vaultSharePrice ) ).toInt256(); } /// @dev Calculates the amount of withdrawal shares that can be redeemed and /// the share proceeds the withdrawal pool should receive given the /// pool's current idle liquidity. We use the following algorithm to /// ensure that the withdrawal pool receives the correct amount of /// shares to (1) preserve the LP share price and (2) pay out as much /// of the idle liquidity as possible to the withdrawal pool: /// /// 1. If `y_s * t_s <= y_l * t_l` or /// `y_max_out(I) >= y_s * t_s - y_l * t_l`, set `dz_max = I` and /// proceed to step (3). Otherwise, proceed to step (2). /// 2. Solve `y_max_out(dz_max) = y_s * t_s - y_l * t_l` for `dz_max` /// using Newton's method. /// 3. Set `dw = (1 - PV(dz_max) / PV(0)) * l`. If `dw <= w`, then /// proceed to step (5). Otherwise, set `dw = w` and continue to /// step (4). /// 4. Solve `PV(0) / l = PV(dz) / (l - dw)` for `dz` using Newton's /// method if `y_l * t_l != y_s * t_s` or directly otherwise. /// 5. Return `dw` and `dz`. /// @param _params The parameters for the distribute excess idle. /// @param _maxIterations The number of iterations to use in the share /// proceeds. This defaults to `LPMath.SHARE_PROCEEDS_MAX_ITERATIONS` /// if the specified value is smaller than the constant. /// @return The amount of withdrawal shares that can be redeemed. /// @return The share proceeds the withdrawal pool should receive. function calculateDistributeExcessIdle( DistributeExcessIdleParams memory _params, uint256 _maxIterations ) external pure returns (uint256, uint256) { // Steps 1 and 2: Calculate the maximum amount the share reserves can be // debited. If the effective share reserves or the maximum share // reserves delta can't be calculated or if the maximum share reserves // delta is zero, idle can't be distributed. (uint256 originalEffectiveShareReserves, bool success) = HyperdriveMath .calculateEffectiveShareReservesSafe( _params.originalShareReserves, _params.originalShareAdjustment ); if (!success) { return (0, 0); } uint256 maxShareReservesDelta; (maxShareReservesDelta, success) = calculateMaxShareReservesDeltaSafe( _params, originalEffectiveShareReserves ); if (!success || maxShareReservesDelta == 0) { return (0, 0); } // Step 3: Calculate the amount of withdrawal shares that can be // redeemed given the maximum share reserves delta. Otherwise, we // proceed to calculating the amount of shares that should be paid out // to redeem all of the withdrawal shares. uint256 withdrawalSharesRedeemed = calculateDistributeExcessIdleWithdrawalSharesRedeemed( _params, maxShareReservesDelta ); // Step 3: If none of the withdrawal shares could be redeemed, then // we're done and we pay out nothing. if (withdrawalSharesRedeemed == 0) { return (0, 0); } // Step 3: Otherwise if this amount is less than or equal to the amount // of withdrawal shares outstanding, then we're done and we pay out the // full maximum share reserves delta. else if ( withdrawalSharesRedeemed <= _params.withdrawalSharesTotalSupply ) { return (withdrawalSharesRedeemed, maxShareReservesDelta); } // Step 3: Otherwise, all of the withdrawal shares are redeemed, and we // need to calculate the amount of shares the withdrawal pool should // receive. else { withdrawalSharesRedeemed = _params.withdrawalSharesTotalSupply; } // Step 4: Solve for the share proceeds that hold the LP share price // invariant after all of the withdrawal shares are redeemed. If the // calculation returns a share proceeds of zero, we can't pay out // anything. uint256 shareProceeds = calculateDistributeExcessIdleShareProceeds( _params, originalEffectiveShareReserves, maxShareReservesDelta, _maxIterations ); if (shareProceeds == 0) { return (0, 0); } // Step 4: If the share proceeds are greater than or equal to the // maximum share reserves delta that was previously calculated, then // we can't distribute excess idle since we ruled out the possibility // of paying out the full maximum share reserves delta in step 3. if (shareProceeds >= maxShareReservesDelta) { return (0, 0); } // Step 5: Return the amount of withdrawal shares redeemed and the // share proceeds. return (withdrawalSharesRedeemed, shareProceeds); } /// @dev Calculates the amount of withdrawal shares that can be redeemed /// given an amount of shares to remove from the share reserves. /// Assuming that dz is the amount of shares to remove from the /// reserves and dl is the amount of LP shares to be burned, we can /// derive the calculation as follows: /// /// PV(0) / l = PV(dx) / (l - dl) /// => /// dl = l - l * (PV(dx) / PV(0)) /// /// We round this calculation up to err on the side of slightly too /// many withdrawal shares being redeemed. /// @param _params The parameters for the present value calculation. /// @param _shareReservesDelta The amount of shares to remove from the /// share reserves. /// @return The amount of withdrawal shares that can be redeemed. function calculateDistributeExcessIdleWithdrawalSharesRedeemed( DistributeExcessIdleParams memory _params, uint256 _shareReservesDelta ) internal pure returns (uint256) { // Calculate the present value after debiting the share reserves delta. bool success; ( _params.presentValueParams.shareReserves, _params.presentValueParams.shareAdjustment, _params.presentValueParams.bondReserves, success ) = calculateUpdateLiquiditySafe( _params.originalShareReserves, _params.originalShareAdjustment, _params.originalBondReserves, _params.presentValueParams.minimumShareReserves, -_shareReservesDelta.toInt256() ); if (!success) { // NOTE: Return zero to indicate that the withdrawal shares redeemed // couldn't be calculated. return 0; } uint256 endingPresentValue; (endingPresentValue, success) = calculatePresentValueSafe( _params.presentValueParams ); if (!success) { // NOTE: Return zero to indicate that the withdrawal shares redeemed // couldn't be calculated. return 0; } // If the ending present value is greater than or equal to the starting // present value, we short-circuit to avoid distributing excess idle. // This edge-case can occur when the share reserves is very close to the // minimum share reserves with a large value of k. if (endingPresentValue >= _params.startingPresentValue) { return 0; } // NOTE: This subtraction is safe since the ending present value is less // than the starting present value and the rhs is rounded down. // // Calculate the amount of withdrawal shares that can be redeemed. uint256 lpTotalSupply = _params.activeLpTotalSupply + _params.withdrawalSharesTotalSupply; return lpTotalSupply - lpTotalSupply.mulDivDown( endingPresentValue, _params.startingPresentValue ); } /// @dev Calculates the share proceeds to distribute to the withdrawal pool /// assuming that all of the outstanding withdrawal shares will be /// redeemed. The share proceeds are calculated such that the LP share /// price is conserved. When we need to round, we round down to err on /// the side of slightly too few shares being paid out. /// @param _params The parameters for the distribute excess idle calculation. /// @param _originalEffectiveShareReserves The original effective share /// reserves. /// @param _maxShareReservesDelta The maximum change in the share reserves /// that can result from distributing excess idle. This provides an /// upper bound on the share proceeds returned from this calculation. /// @param _maxIterations The number of iterations to use in the share /// proceeds. This defaults to `LPMath.SHARE_PROCEEDS_MAX_ITERATIONS` /// if the specified value is smaller than the constant. /// @return The share proceeds to distribute to the withdrawal pool. function calculateDistributeExcessIdleShareProceeds( DistributeExcessIdleParams memory _params, uint256 _originalEffectiveShareReserves, uint256 _maxShareReservesDelta, uint256 _maxIterations ) internal pure returns (uint256) { // Calculate the LP total supply. uint256 lpTotalSupply = _params.activeLpTotalSupply + _params.withdrawalSharesTotalSupply; // NOTE: Round the initial guess down to avoid overshooting. // // We make an initial guess for Newton's method by assuming that the // ratio of the share reserves delta to the withdrawal shares // outstanding is equal to the LP share price. In reality, the // withdrawal pool should receive more than this, but it's a good // starting point. The calculation is: // // x_0 = w * (PV(0) / l) uint256 shareProceeds = _params.withdrawalSharesTotalSupply.mulDivDown( _params.startingPresentValue, lpTotalSupply ); // If the pool is net neutral, the initial guess is equal to the final // result. if (_params.netCurveTrade == 0) { return shareProceeds; } // Proceed with Newton's method. The objective function, `F(x)`, is // given by: // // F(x) = PV(x) * l - PV(0) * (l - w) // // Newton's method will terminate as soon as the current iteration is // within the minimum tolerance or the maximum number of iterations has // been reached. int256 smallestDelta; uint256 closestShareProceeds; uint256 closestPresentValue; DistributeExcessIdleParams memory params = _params; // avoid stack-too-deep if (_maxIterations < SHARE_PROCEEDS_MAX_ITERATIONS) { _maxIterations = SHARE_PROCEEDS_MAX_ITERATIONS; } for (uint256 i = 0; i < _maxIterations; i++) { // Clamp the share proceeds to the max share reserves delta since // values above this threshold are always invalid. shareProceeds = shareProceeds.min(_maxShareReservesDelta); // Simulate applying the share proceeds to the reserves. bool success; ( params.presentValueParams.shareReserves, params.presentValueParams.shareAdjustment, params.presentValueParams.bondReserves, success ) = calculateUpdateLiquiditySafe( params.originalShareReserves, params.originalShareAdjustment, params.originalBondReserves, params.presentValueParams.minimumShareReserves, -shareProceeds.toInt256() ); if (!success) { // NOTE: If the updated reserves can't be calculated, we can't // continue the calculation. Return 0 to indicate that the share // proceeds couldn't be calculated. return 0; } // Recalculate the present value. uint256 presentValue; (presentValue, success) = calculatePresentValueSafe( _params.presentValueParams ); if (!success) { // NOTE: If the present value can't be calculated, we can't // continue the calculation. Return 0 to indicate that the share // proceeds couldn't be calculated. return 0; } // Short-circuit if we are within the minimum tolerance. if ( shouldShortCircuitDistributeExcessIdleShareProceeds( params, presentValue, lpTotalSupply ) ) { return shareProceeds; } // If the pool is net long, we can solve for the next iteration of // Newton's method directly when the net curve trade is greater than // or equal to the max bond amount. if (params.netCurveTrade > 0) { // Calculate the max bond amount. If the calculation fails, we // return a failure flag. uint256 maxBondAmount; (maxBondAmount, success) = YieldSpaceMath .calculateMaxSellBondsInSafe( params.presentValueParams.shareReserves, params.presentValueParams.shareAdjustment, params.presentValueParams.bondReserves, params.presentValueParams.minimumShareReserves, ONE - params.presentValueParams.timeStretch, params.presentValueParams.vaultSharePrice, params.presentValueParams.initialVaultSharePrice ); if (!success) { // NOTE: If the max bond amount couldn't be calculated, we // can't continue the calculation. Return 0 to indicate that // the share proceeds couldn't be calculated. return 0; } // If the net curve trade is greater than or equal to the max // bond amount, we can solve directly for the share proceeds. if (uint256(params.netCurveTrade) >= maxBondAmount) { // Solve the objective function directly assuming that it is // linear with respect to the share proceeds. ( shareProceeds, success ) = calculateDistributeExcessIdleShareProceedsNetLongEdgeCaseSafe( params ); if (!success) { // NOTE: Return 0 to indicate that the share proceeds // couldn't be calculated. return 0; } // Simulate applying the share proceeds to the reserves and // recalculate the max bond amount. ( params.presentValueParams.shareReserves, params.presentValueParams.shareAdjustment, params.presentValueParams.bondReserves, success ) = calculateUpdateLiquiditySafe( params.originalShareReserves, params.originalShareAdjustment, params.originalBondReserves, params.presentValueParams.minimumShareReserves, -shareProceeds.toInt256() ); if (!success) { // NOTE: Return 0 to indicate that the share proceeds // couldn't be calculated. return 0; } (maxBondAmount, success) = YieldSpaceMath .calculateMaxSellBondsInSafe( params.presentValueParams.shareReserves, params.presentValueParams.shareAdjustment, params.presentValueParams.bondReserves, params.presentValueParams.minimumShareReserves, ONE - params.presentValueParams.timeStretch, params.presentValueParams.vaultSharePrice, params.presentValueParams.initialVaultSharePrice ); if (!success) { // NOTE: Return 0 to indicate that the share proceeds // couldn't be calculated. return 0; } // If the max bond amount is less than or equal to the net // curve trade, then Newton's method has terminated since // proceeding to the next step would result in reaching the // same point. if (maxBondAmount <= uint256(_params.netCurveTrade)) { return shareProceeds; } // Otherwise, we continue to the next iteration of Newton's // method. else { continue; } } } // We calculate the derivative of F(x) using the derivative of // `calculateSharesOutGivenBondsIn` when the pool is net long or // the derivative of `calculateSharesInGivenBondsOut`. when the pool // is net short. uint256 derivative; ( derivative, success ) = calculateSharesDeltaGivenBondsDeltaDerivativeSafe( params, _originalEffectiveShareReserves, params.netCurveTrade ); if (!success || derivative >= ONE) { // NOTE: Return 0 to indicate that the share proceeds // couldn't be calculated. return 0; } unchecked { derivative = ONE - derivative; } // NOTE: Round the delta down to avoid overshooting. // // Calculate the objective function's value. If the value's magnitude // is smaller than the previous smallest value, then we update the // value and record the share proceeds. We'll ultimately return the // share proceeds that resulted in the smallest value. int256 delta = presentValue.mulDown(lpTotalSupply).toInt256() - params .startingPresentValue .mulUp(params.activeLpTotalSupply) .toInt256(); if (smallestDelta == 0 || delta.abs() < smallestDelta.abs()) { smallestDelta = delta; closestShareProceeds = shareProceeds; closestPresentValue = presentValue; } // We calculate the updated share proceeds `x_n+1` by proceeding // with Newton's method. This is given by: // // x_n+1 = x_n - F(x_n) / F'(x_n) if (delta > 0) { // NOTE: Round the quotient down to avoid overshooting. shareProceeds = shareProceeds + uint256(delta).divDown(derivative).divDown(lpTotalSupply); } else if (delta < 0) { // NOTE: Round the quotient down to avoid overshooting. uint256 delta_ = uint256(-delta).divDown(derivative).divDown( lpTotalSupply ); if (delta_ < shareProceeds) { unchecked { shareProceeds = shareProceeds - delta_; } } else { // NOTE: Returning 0 to indicate that the share proceeds // couldn't be calculated. return 0; } } else { break; } } // Calculate the present value after applying the share proceeds. bool success_; ( params.presentValueParams.shareReserves, params.presentValueParams.shareAdjustment, params.presentValueParams.bondReserves, success_ ) = calculateUpdateLiquiditySafe( params.originalShareReserves, params.originalShareAdjustment, params.originalBondReserves, params.presentValueParams.minimumShareReserves, -shareProceeds.toInt256() ); if (!success_) { // NOTE: Return 0 to indicate that the share proceeds couldn't be // calculated. return 0; } uint256 presentValue_ = calculatePresentValue( params.presentValueParams ); // Check to see if the current share proceeds is closer to the optimal // value than the previous closest value. We'll choose whichever of the // share proceeds that is closer to the optimal value. int256 lastDelta = presentValue_.mulDown(lpTotalSupply).toInt256() - params .startingPresentValue .mulUp(params.activeLpTotalSupply) .toInt256(); if (lastDelta.abs() < smallestDelta.abs()) { closestShareProceeds = shareProceeds; closestPresentValue = presentValue_; } // Verify that the LP share price was conserved within a reasonable // tolerance. if ( // NOTE: Round down to make the check stricter. closestPresentValue.divDown(params.activeLpTotalSupply) < params.startingPresentValue.mulDivUp( ONE - SHARE_PROCEEDS_TOLERANCE, lpTotalSupply ) || // NOTE: Round up to make the check stricter. closestPresentValue.divUp(params.activeLpTotalSupply) > params.startingPresentValue.mulDivDown( ONE + SHARE_PROCEEDS_TOLERANCE, lpTotalSupply ) ) { // NOTE: Return 0 to indicate that the share proceeds couldn't be // calculated. return 0; } return closestShareProceeds; } /// @dev One of the edge cases that occurs when using Newton's method for /// the share proceeds while distributing excess idle is when the net /// curve trade is larger than the max bond amount. In this case, the /// the present value simplifies to the following: /// /// PV(dz) = (z - dz) + net_c(dz) + net_f - z_min /// = (z - dz) - z_max_out(dz) + net_f - z_min /// /// There are two cases to evaluate: /// /// (1) zeta > 0: /// /// z_max_out(dz) = ((z - dz) / z) * (z - zeta) - z_min /// /// => /// /// PV(dz) = zeta * ((z - dz) / z) + net_f /// /// (2) zeta <= 0: /// /// z_max_out(dz) = (z - dz) - z_min /// /// => /// /// PV(dz) = net_f /// /// Since the present value is constant with respect to the share /// proceeds in case 2, Newton's method has achieved a stationary point /// and can't proceed. On the other hand, the present value is linear /// with respect to the share proceeds, and we can solve for the next /// step of Newton's method directly as follows: /// /// PV(0) / l = PV(dz) / (l - w) /// /// => /// /// dz = z - ((PV(0) / l) * (l - w) - net_f) / (zeta / z) /// /// We round the share proceeds down to err on the side of the /// withdrawal pool receiving slightly less shares. /// @param _params The parameters for the calculation. /// @return The share proceeds. /// @return A flag indicating whether the calculation was successful. function calculateDistributeExcessIdleShareProceedsNetLongEdgeCaseSafe( DistributeExcessIdleParams memory _params ) internal pure returns (uint256, bool) { // If the original share adjustment is zero or negative, we cannot // calculate the share proceeds. This should never happen, but for // safety we return a failure flag and break the loop at this point. if (_params.originalShareAdjustment <= 0) { return (0, false); } // Calculate the net flat trade. int256 netFlatTrade = calculateNetFlatTrade(_params.presentValueParams); // NOTE: Round up since this is the rhs of the final subtraction. // // rhs = (PV(0) / l) * (l - w) - net_f uint256 rhs = _params.startingPresentValue.mulDivUp( _params.activeLpTotalSupply, _params.activeLpTotalSupply + _params.withdrawalSharesTotalSupply ); if (netFlatTrade >= 0) { if (uint256(netFlatTrade) < rhs) { unchecked { rhs -= uint256(netFlatTrade); } } else { // NOTE: Return a failure flag if computing the rhs would // underflow. return (0, false); } } else { rhs += uint256(-netFlatTrade); } // NOTE: Round up since this is the rhs of the final subtraction. // // rhs = ((PV(0) / l) * (l - w) - net_f) / (zeta / z) rhs = _params.originalShareReserves.mulDivUp( rhs, uint256(_params.originalShareAdjustment) ); // share proceeds = z - rhs if (_params.originalShareReserves < rhs) { return (0, false); } unchecked { return (_params.originalShareReserves - rhs, true); } } /// @dev Checks to see if we should short-circuit the iterative calculation /// of the share proceeds when distributing excess idle liquidity. This /// verifies that the ending LP share price is greater than or equal to /// the starting LP share price and less than or equal to the starting /// LP share price plus the minimum tolerance. /// @param _params The parameters for the calculation. /// @param _lpTotalSupply The total supply of LP shares. /// @param _presentValue The present value of the pool at this iteration of /// the calculation. /// @return A flag indicating whether or not we should short-circuit the /// calculation. function shouldShortCircuitDistributeExcessIdleShareProceeds( DistributeExcessIdleParams memory _params, uint256 _lpTotalSupply, uint256 _presentValue ) internal pure returns (bool) { return // Ensure that new LP share price is greater than or equal to the // previous LP share price: // // PV_1 / l_1 >= PV_0 / l_0 // // NOTE: Round the LHS down to make the check stricter. _presentValue.divDown(_params.activeLpTotalSupply) >= // NOTE: Round the RHS up to make the check stricter. _params.startingPresentValue.divUp(_lpTotalSupply) && // Ensure that new LP share price is less than or equal to the // previous LP share price plus the minimum tolerance: // // PV_1 / l_1 <= (PV_0 / l_0) * (1 + tolerance) // // NOTE: Round the LHS up to make the check stricter. _presentValue.divUp(_params.activeLpTotalSupply) <= // NOTE: Round the RHS down to make the check stricter. (ONE + SHARE_PROCEEDS_SHORT_CIRCUIT_TOLERANCE).mulDivDown( _params.startingPresentValue, _lpTotalSupply ); } /// @dev Calculates the upper bound on the share proceeds of distributing /// excess idle. When the pool is net long or net neutral, the upper /// bound is the amount of idle liquidity. When the pool is net short, /// the upper bound is the share reserves delta that results in the /// maximum amount of bonds that can be purchased being equal to the /// net short position. /// @param _params The parameters for the distribute excess idle calculation. /// @param _originalEffectiveShareReserves The original effective share /// reserves. /// @return maxShareReservesDelta The upper bound on the share proceeds. /// @return success A flag indicating if the calculation succeeded. function calculateMaxShareReservesDeltaSafe( DistributeExcessIdleParams memory _params, uint256 _originalEffectiveShareReserves ) internal pure returns (uint256 maxShareReservesDelta, bool success) { // If the net curve position is zero or net long, then the maximum // share reserves delta is equal to the pool's idle. if (_params.netCurveTrade >= 0) { return (_params.idle, true); } uint256 netCurveTrade = uint256(-_params.netCurveTrade); // Calculate the max bond amount. If the calculation fails, we return a // failure flag. If the calculation succeeds but the max bond amount // is zero, then we return a failure flag since we can't divide by zero. uint256 maxBondAmount; (maxBondAmount, success) = YieldSpaceMath.calculateMaxBuyBondsOutSafe( _originalEffectiveShareReserves, _params.originalBondReserves, ONE - _params.presentValueParams.timeStretch, _params.presentValueParams.vaultSharePrice, _params.presentValueParams.initialVaultSharePrice ); if (!success || maxBondAmount == 0) { return (0, false); } // We can solve for the maximum share reserves delta in one shot using // the fact that the maximum amount of bonds that can be purchased is // linear with respect to the scaling factor applied to the reserves. // In other words, if s > 0 is a factor scaling the reserves, we have // the following relationship: // // y_out^max(s * z, s * y, s * zeta) = s * y_out^max(z, y, zeta) // // We solve for the maximum share reserves delta by finding the scaling // factor that results in the maximum amount of bonds that can be // purchased being equal to the net curve trade. We can derive this // maximum using the linearity property mentioned above as follows: // // y_out^max(s * z, s * y, s * zeta) - netCurveTrade = 0 // => // s * y_out^max(z, y, zeta) - netCurveTrade = 0 // => // s = netCurveTrade / y_out^max(z, y, zeta) uint256 maxScalingFactor = netCurveTrade.divUp(maxBondAmount); // Using the maximum scaling factor, we can calculate the maximum share // reserves delta as: // // maxShareReservesDelta = z * (1 - s) if (maxScalingFactor <= ONE) { unchecked { maxShareReservesDelta = ONE - maxScalingFactor; } maxShareReservesDelta = maxShareReservesDelta.mulDown( _params.originalShareReserves ); } else { // NOTE: If the max scaling factor is greater than one, the // calculation fails and we return a failure flag. return (0, false); } // If the maximum share reserves delta is greater than the idle, then // the maximum share reserves delta is equal to the idle. if (maxShareReservesDelta > _params.idle) { return (_params.idle, true); } return (maxShareReservesDelta, true); } /// @dev Given a signed bond amount, this function calculates the negation /// of the derivative of `calculateSharesOutGivenBondsIn` when the /// bond amount is positive or the derivative of /// `calculateSharesInGivenBondsOut` when the bond amount is negative. /// In both cases, the calculation is given by: /// /// derivative = (1 - zeta / z) * ( /// 1 - (1 / c) * ( /// c * (mu * z_e(x)) ** -t_s + /// (y / z_e) * y(x) ** -t_s - /// (y / z_e) * (y(x) + dy) ** -t_s /// ) * ( /// (mu / c) * (k(x) - (y(x) + dy) ** (1 - t_s)) /// ) ** (t_s / (1 - t_s)) /// ) /// /// This quantity is used in Newton's method to search for the optimal /// share proceeds. When the pool is net long, We can express the /// derivative of the objective function F(x) by the derivative /// -z_out'(x) that this function returns: /// /// -F'(x) = l * -PV'(x) /// = l * (1 - net_c'(x)) /// = l * (1 + z_out'(x)) /// = l * (1 - derivative) /// /// When the pool is net short, we can express the derivative of the /// objective function F(x) by the derivative z_in'(x) that this /// function returns: /// /// -F'(x) = l * -PV'(x) /// = l * (1 - net_c'(x)) /// = l * (1 - z_in'(x)) /// = l * (1 - derivative) /// /// With these calculations in mind, this function rounds its result /// down so that F'(x) is overestimated. Since F'(x) is in the /// denominator of Newton's method, overestimating F'(x) helps to avoid /// overshooting the optimal solution. /// @param _params The parameters for the calculation. /// @param _originalEffectiveShareReserves The original effective share /// reserves. /// @param _bondAmount The amount of bonds that are being bought or sold. /// @return The negation of the derivative of /// `calculateSharesOutGivenBondsIn` when the bond amount is /// positive or the derivative of `calculateSharesInGivenBondsOut` /// when the bond amount is negative. /// @return A flag indicating whether the derivative could be computed. function calculateSharesDeltaGivenBondsDeltaDerivativeSafe( DistributeExcessIdleParams memory _params, uint256 _originalEffectiveShareReserves, int256 _bondAmount ) internal pure returns (uint256, bool) { // Calculate the bond reserves after the bond amount is applied. uint256 bondReserves; if (_bondAmount >= 0) { bondReserves = _params.presentValueParams.bondReserves + uint256(_bondAmount); } else { uint256 bondAmount = uint256(-_bondAmount); if (bondAmount < _params.presentValueParams.bondReserves) { unchecked { bondReserves = _params.presentValueParams.bondReserves - bondAmount; } } else { // NOTE: Return a failure flag if calculating the bond reserves // would underflow. return (0, false); } } // NOTE: Round up since this is on the rhs of the final subtraction. // // derivative = c * (mu * z_e(x)) ** -t_s + // (y / z_e) * (y(x)) ** -t_s - // (y / z_e) * (y(x) + dy) ** -t_s (uint256 effectiveShareReserves, bool success) = HyperdriveMath .calculateEffectiveShareReservesSafe( _params.presentValueParams.shareReserves, _params.presentValueParams.shareAdjustment ); if (!success) { return (0, false); } uint256 derivative = _params.presentValueParams.vaultSharePrice.divUp( _params .presentValueParams .initialVaultSharePrice .mulDown(effectiveShareReserves) .pow(_params.presentValueParams.timeStretch) ) + _params.originalBondReserves.divUp( _originalEffectiveShareReserves.mulDown( _params.presentValueParams.bondReserves.pow( _params.presentValueParams.timeStretch ) ) ); // NOTE: Round down this rounds the subtraction up. uint256 rhs = _params.originalBondReserves.divDown( _originalEffectiveShareReserves.mulUp( bondReserves.pow(_params.presentValueParams.timeStretch) ) ); if (derivative < rhs) { return (0, false); } unchecked { derivative -= rhs; } // NOTE: Round up since this is on the rhs of the final subtraction. // // inner = ( // (mu / c) * (k(x) - (y(x) + dy) ** (1 - t_s)) // ) ** (t_s / (1 - t_s)) uint256 k = YieldSpaceMath.kUp( effectiveShareReserves, _params.presentValueParams.bondReserves, ONE - _params.presentValueParams.timeStretch, _params.presentValueParams.vaultSharePrice, _params.presentValueParams.initialVaultSharePrice ); uint256 inner = bondReserves.pow( ONE - _params.presentValueParams.timeStretch ); if (k < inner) { // NOTE: In this case, we shouldn't proceed with distributing excess // idle since the derivative couldn't be computed. return (0, false); } unchecked { inner = k - inner; } inner = inner.mulDivUp( _params.presentValueParams.initialVaultSharePrice, _params.presentValueParams.vaultSharePrice ); if (inner >= ONE) { // NOTE: Round the exponent up since this rounds the result up. inner = inner.pow( _params.presentValueParams.timeStretch.divUp( ONE - _params.presentValueParams.timeStretch ) ); } else { // NOTE: Round the exponent down since this rounds the result up. inner = inner.pow( _params.presentValueParams.timeStretch.divDown( ONE - _params.presentValueParams.timeStretch ) ); } // NOTE: Round up since this is on the rhs of the final subtraction. derivative = derivative.mulDivUp( inner, _params.presentValueParams.vaultSharePrice ); // derivative = 1 - derivative if (ONE > derivative) { unchecked { derivative = ONE - derivative; } } else { // NOTE: Small rounding errors can result in the derivative being // slightly (on the order of a few wei) greater than 1. In this case, // we return 0 since we should proceed with Newton's method. return (0, true); } // NOTE: Round down to round the final result down. // // derivative = derivative * (1 - (zeta / z)) if (_params.originalShareAdjustment >= 0) { rhs = uint256(_params.originalShareAdjustment).divUp( _params.originalShareReserves ); if (rhs > ONE) { // NOTE: Return a failure flag if the calculation would // underflow. return (0, false); } unchecked { rhs = ONE - rhs; } derivative = derivative.mulDown(rhs); } else { derivative = derivative.mulDown( ONE + uint256(-_params.originalShareAdjustment).divDown( _params.originalShareReserves ) ); } return (derivative, true); } }
/// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; import { IHyperdrive } from "../interfaces/IHyperdrive.sol"; /// @notice Safe unsigned integer casting library that reverts on overflow. /// @author Inspired by OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/SafeCast.sol) library SafeCast { /// @notice This function safely casts a uint256 to a uint112. /// @param x The uint256 to cast to uint112. /// @return y The uint112 casted from x. function toUint112(uint256 x) internal pure returns (uint112 y) { if (x > type(uint112).max) { revert IHyperdrive.UnsafeCastToUint112(); } y = uint112(x); } /// @notice This function safely casts a uint256 to a uint128. /// @param x The uint256 to cast to uint128. /// @return y The uint128 casted from x. function toUint128(uint256 x) internal pure returns (uint128 y) { if (x > type(uint128).max) { revert IHyperdrive.UnsafeCastToUint128(); } y = uint128(x); } /// @notice This function safely casts an int256 to an uint256. /// @param x The int256 to cast to uint256. /// @return y The uint256 casted from x. function toUint256(int256 x) internal pure returns (uint256 y) { if (x < 0) { revert IHyperdrive.UnsafeCastToUint256(); } y = uint256(x); } /// @notice This function safely casts an uint256 to an int128. /// @param x The uint256 to cast to int128. /// @return y The int128 casted from x. function toInt128(uint256 x) internal pure returns (int128 y) { if (x > uint128(type(int128).max)) { revert IHyperdrive.UnsafeCastToInt128(); } y = int128(int256(x)); } /// @notice This function safely casts an int256 to an int128. /// @param x The int256 to cast to int128. /// @return y The int128 casted from x. function toInt128(int256 x) internal pure returns (int128 y) { if (x < type(int128).min || x > type(int128).max) { revert IHyperdrive.UnsafeCastToInt128(); } y = int128(x); } /// @notice This function safely casts an uint256 to an int256. /// @param x The uint256 to cast to int256. /// @return y The int256 casted from x. function toInt256(uint256 x) internal pure returns (int256 y) { if (x > uint256(type(int256).max)) { revert IHyperdrive.UnsafeCastToInt256(); } y = int256(x); } }
/// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; import { Errors } from "./Errors.sol"; import { FixedPointMath, ONE } from "./FixedPointMath.sol"; import { HyperdriveMath } from "./HyperdriveMath.sol"; /// @author DELV /// @title YieldSpaceMath /// @notice Math for the YieldSpace pricing model. /// @custom:disclaimer The language used in this code is for coding convenience /// only, and is not intended to, and does not, have any /// particular legal or regulatory significance. /// /// @dev It is advised for developers to attain the pre-requisite knowledge /// of how this implementation works on the mathematical level. This /// excerpt attempts to document this pre-requisite knowledge explaining /// the underpinning mathematical concepts in an understandable manner and /// relating it directly to the code implementation. /// This implementation is based on a paper called "YieldSpace with Yield /// Bearing Vaults" or more casually "Modified YieldSpace". It can be /// found at the following link. /// /// https://hackmd.io/lRZ4mgdrRgOpxZQXqKYlFw?view /// /// That paper builds on the original YieldSpace paper, "YieldSpace: /// An Automated Liquidity Provider for Fixed Yield Tokens". It can be /// found at the following link: /// /// https://yieldprotocol.com/YieldSpace.pdf library YieldSpaceMath { using FixedPointMath for uint256; /// @dev Calculates the amount of bonds a user will receive from the pool by /// providing a specified amount of shares. We underestimate the amount /// of bonds out. /// @param ze The effective share reserves. /// @param y The bond reserves. /// @param dz The amount of shares paid to the pool. /// @param t The time elapsed since the term's start. /// @param c The vault share price. /// @param mu The initial vault share price. /// @return result The amount of bonds the trader receives. function calculateBondsOutGivenSharesInDown( uint256 ze, uint256 y, uint256 dz, uint256 t, uint256 c, uint256 mu ) internal pure returns (uint256 result) { bool success; (result, success) = calculateBondsOutGivenSharesInDownSafe( ze, y, dz, t, c, mu ); if (!success) { Errors.throwInsufficientLiquidityError(); } } /// @dev Calculates the amount of bonds a user will receive from the pool by /// providing a specified amount of shares. This function returns a /// success flag instead of reverting. We underestimate the amount /// of bonds out. /// @param ze The effective share reserves. /// @param y The bond reserves. /// @param dz The amount of shares paid to the pool. /// @param t The time elapsed since the term's start. /// @param c The vault share price. /// @param mu The initial vault share price. /// @return The amount of bonds the trader receives. /// @return A flag indicating if the calculation succeeded. function calculateBondsOutGivenSharesInDownSafe( uint256 ze, uint256 y, uint256 dz, uint256 t, uint256 c, uint256 mu ) internal pure returns (uint256, bool) { // NOTE: We round k up to make the rhs of the equation larger. // // k = (c / µ) * (µ * ze)^(1 - t) + y^(1 - t) uint256 k = kUp(ze, y, t, c, mu); // NOTE: We round ze down to make the rhs of the equation larger. // // (µ * (ze + dz))^(1 - t) ze = mu.mulDown(ze + dz).pow(t); // (c / µ) * (µ * (ze + dz))^(1 - t) ze = c.mulDivDown(ze, mu); // If k < ze, we return a failure flag since the calculation would have // underflowed. if (k < ze) { return (0, false); } // NOTE: We round _y up to make the rhs of the equation larger. // // (k - (c / µ) * (µ * (ze + dz))^(1 - t))^(1 / (1 - t)) uint256 _y; unchecked { _y = k - ze; } if (_y >= ONE) { // Rounding up the exponent results in a larger result. _y = _y.pow(ONE.divUp(t)); } else { // Rounding down the exponent results in a larger result. _y = _y.pow(ONE.divDown(t)); } // If y < _y, we return a failure flag since the calculation would have // underflowed. if (y < _y) { return (0, false); } // Δy = y - (k - (c / µ) * (µ * (ze + dz))^(1 - t))^(1 / (1 - t)) unchecked { return (y - _y, true); } } /// @dev Calculates the amount of shares a user must provide the pool to /// receive a specified amount of bonds. We overestimate the amount of /// shares in. /// @param ze The effective share reserves. /// @param y The bond reserves. /// @param dy The amount of bonds paid to the trader. /// @param t The time elapsed since the term's start. /// @param c The vault share price. /// @param mu The initial vault share price. /// @return result The amount of shares the trader pays. function calculateSharesInGivenBondsOutUp( uint256 ze, uint256 y, uint256 dy, uint256 t, uint256 c, uint256 mu ) internal pure returns (uint256 result) { bool success; (result, success) = calculateSharesInGivenBondsOutUpSafe( ze, y, dy, t, c, mu ); if (!success) { Errors.throwInsufficientLiquidityError(); } } /// @dev Calculates the amount of shares a user must provide the pool to /// receive a specified amount of bonds. This function returns a /// success flag instead of reverting. We overestimate the amount of /// shares in. /// @param ze The effective share reserves. /// @param y The bond reserves. /// @param dy The amount of bonds paid to the trader. /// @param t The time elapsed since the term's start. /// @param c The vault share price. /// @param mu The initial vault share price. /// @return The amount of shares the trader pays. /// @return A flag indicating if the calculation succeeded. function calculateSharesInGivenBondsOutUpSafe( uint256 ze, uint256 y, uint256 dy, uint256 t, uint256 c, uint256 mu ) internal pure returns (uint256, bool) { // NOTE: We round k up to make the lhs of the equation larger. // // k = (c / µ) * (µ * ze)^(1 - t) + y^(1 - t) uint256 k = kUp(ze, y, t, c, mu); // If y < dy, we return a failure flag since the calculation would have // underflowed. if (y < dy) { return (0, false); } // (y - dy)^(1 - t) unchecked { y -= dy; } y = y.pow(t); // If k < y, we return a failure flag since the calculation would have // underflowed. if (k < y) { return (0, false); } // NOTE: We round _z up to make the lhs of the equation larger. // // ((k - (y - dy)^(1 - t) ) / (c / µ))^(1 / (1 - t)) uint256 _z; unchecked { _z = k - y; } _z = _z.mulDivUp(mu, c); if (_z >= ONE) { // Rounding up the exponent results in a larger result. _z = _z.pow(ONE.divUp(t)); } else { // Rounding down the exponent results in a larger result. _z = _z.pow(ONE.divDown(t)); } // ((k - (y - dy)^(1 - t) ) / (c / µ))^(1 / (1 - t))) / µ _z = _z.divUp(mu); // If _z < ze, we return a failure flag since the calculation would have // underflowed. if (_z < ze) { return (0, false); } // Δz = (((k - (y - dy)^(1 - t) ) / (c / µ))^(1 / (1 - t))) / µ - ze unchecked { return (_z - ze, true); } } /// @dev Calculates the amount of shares a user must provide the pool to /// receive a specified amount of bonds. We underestimate the amount of /// shares in. /// @param ze The effective share reserves. /// @param y The bond reserves. /// @param dy The amount of bonds paid to the trader. /// @param t The time elapsed since the term's start. /// @param c The vault share price. /// @param mu The initial vault share price. /// @return The amount of shares the user pays. function calculateSharesInGivenBondsOutDown( uint256 ze, uint256 y, uint256 dy, uint256 t, uint256 c, uint256 mu ) internal pure returns (uint256) { // NOTE: We round k down to make the lhs of the equation smaller. // // k = (c / µ) * (µ * ze)^(1 - t) + y^(1 - t) uint256 k = kDown(ze, y, t, c, mu); // If y < dy, we have no choice but to revert. if (y < dy) { Errors.throwInsufficientLiquidityError(); } // (y - dy)^(1 - t) unchecked { y -= dy; } y = y.pow(t); // If k < y, we have no choice but to revert. if (k < y) { Errors.throwInsufficientLiquidityError(); } // NOTE: We round _z down to make the lhs of the equation smaller. // // _z = ((k - (y - dy)^(1 - t) ) / (c / µ))^(1 / (1 - t)) uint256 _z; unchecked { _z = k - y; } _z = _z.mulDivDown(mu, c); if (_z >= ONE) { // Rounding down the exponent results in a smaller result. _z = _z.pow(ONE.divDown(t)); } else { // Rounding up the exponent results in a smaller result. _z = _z.pow(ONE.divUp(t)); } // ((k - (y - dy)^(1 - t) ) / (c / µ))^(1 / (1 - t))) / µ _z = _z.divDown(mu); // If _z < ze, we have no choice but to revert. if (_z < ze) { Errors.throwInsufficientLiquidityError(); } // Δz = (((k - (y - dy)^(1 - t) ) / (c / µ))^(1 / (1 - t))) / µ - ze unchecked { return _z - ze; } } /// @dev Calculates the amount of shares a user will receive from the pool /// by providing a specified amount of bonds. This function reverts if /// an integer overflow or underflow occurs. We underestimate the /// amount of shares out. /// @param ze The effective share reserves. /// @param y The bond reserves. /// @param dy The amount of bonds paid to the pool. /// @param t The time elapsed since the term's start. /// @param c The vault share price. /// @param mu The initial vault share price. /// @return result The amount of shares the user receives. function calculateSharesOutGivenBondsInDown( uint256 ze, uint256 y, uint256 dy, uint256 t, uint256 c, uint256 mu ) internal pure returns (uint256 result) { bool success; (result, success) = calculateSharesOutGivenBondsInDownSafe( ze, y, dy, t, c, mu ); if (!success) { Errors.throwInsufficientLiquidityError(); } } /// @dev Calculates the amount of shares a user will receive from the pool /// by providing a specified amount of bonds. This function returns a /// success flag instead of reverting. We underestimate the amount of /// shares out. /// @param ze The effective share reserves. /// @param y The bond reserves. /// @param dy The amount of bonds paid to the pool. /// @param t The time elapsed since the term's start. /// @param c The vault share price. /// @param mu The initial vault share price. /// @return The amount of shares the user receives /// @return A flag indicating if the calculation succeeded. function calculateSharesOutGivenBondsInDownSafe( uint256 ze, uint256 y, uint256 dy, uint256 t, uint256 c, uint256 mu ) internal pure returns (uint256, bool) { // NOTE: We round k up to make the rhs of the equation larger. // // k = (c / µ) * (µ * ze)^(1 - t) + y^(1 - t) uint256 k = kUp(ze, y, t, c, mu); // (y + dy)^(1 - t) y = (y + dy).pow(t); // If k is less than y, we return with a failure flag. if (k < y) { return (0, false); } // NOTE: We round _z up to make the rhs of the equation larger. // // ((k - (y + dy)^(1 - t)) / (c / µ))^(1 / (1 - t))) uint256 _z; unchecked { _z = k - y; } _z = _z.mulDivUp(mu, c); if (_z >= ONE) { // Rounding the exponent up results in a larger outcome. _z = _z.pow(ONE.divUp(t)); } else { // Rounding the exponent down results in a larger outcome. _z = _z.pow(ONE.divDown(t)); } // ((k - (y + dy)^(1 - t) ) / (c / µ))^(1 / (1 - t))) / µ _z = _z.divUp(mu); // If ze is less than _z, we return a failure flag since the calculation // underflowed. if (ze < _z) { return (0, false); } // Δz = ze - ((k - (y + dy)^(1 - t) ) / (c / µ))^(1 / (1 - t)) / µ unchecked { return (ze - _z, true); } } /// @dev Calculates the share payment required to purchase the maximum /// amount of bonds from the pool. This function returns a success flag /// instead of reverting. We round so that the max buy amount is /// underestimated. /// @param ze The effective share reserves. /// @param y The bond reserves. /// @param t The time elapsed since the term's start. /// @param c The vault share price. /// @param mu The initial vault share price. /// @return The share payment to purchase the maximum amount of bonds. /// @return A flag indicating if the calculation succeeded. function calculateMaxBuySharesInSafe( uint256 ze, uint256 y, uint256 t, uint256 c, uint256 mu ) internal pure returns (uint256, bool) { // We solve for the maximum buy using the constraint that the pool's // spot price can never exceed 1. We do this by noting that a spot price // of 1, ((mu * ze') / y') ** tau = 1, implies that mu * ze' = y'. This // simplifies YieldSpace to: // // k = ((c / mu) + 1) * (mu * ze') ** (1 - tau), // // This gives us the maximum effective share reserves of: // // ze' = (1 / mu) * (k / ((c / mu) + 1)) ** (1 / (1 - tau)). uint256 k = kDown(ze, y, t, c, mu); uint256 optimalZe = k.divDown(c.divUp(mu) + ONE); if (optimalZe >= ONE) { // Rounding the exponent down results in a smaller outcome. optimalZe = optimalZe.pow(ONE.divDown(t)); } else { // Rounding the exponent up results in a smaller outcome. optimalZe = optimalZe.pow(ONE.divUp(t)); } optimalZe = optimalZe.divDown(mu); // The optimal trade size is given by dz = ze' - ze. If the calculation // underflows, we return a failure flag. if (optimalZe < ze) { return (0, false); } unchecked { return (optimalZe - ze, true); } } /// @dev Calculates the maximum amount of bonds that can be purchased with /// the specified reserves. This function returns a success flag /// instead of reverting. We round so that the max buy amount is /// underestimated. /// @param ze The effective share reserves. /// @param y The bond reserves. /// @param t The time elapsed since the term's start. /// @param c The vault share price. /// @param mu The initial vault share price. /// @return The maximum amount of bonds that can be purchased. /// @return A flag indicating if the calculation succeeded. function calculateMaxBuyBondsOutSafe( uint256 ze, uint256 y, uint256 t, uint256 c, uint256 mu ) internal pure returns (uint256, bool) { // We can use the same derivation as in `calculateMaxBuySharesIn` to // calculate the minimum bond reserves as: // // y' = (k / ((c / mu) + 1)) ** (1 / (1 - tau)). uint256 k = kUp(ze, y, t, c, mu); uint256 optimalY = k.divUp(c.divDown(mu) + ONE); if (optimalY >= ONE) { // Rounding the exponent up results in a larger outcome. optimalY = optimalY.pow(ONE.divUp(t)); } else { // Rounding the exponent down results in a larger outcome. optimalY = optimalY.pow(ONE.divDown(t)); } // The optimal trade size is given by dy = y - y'. If the calculation // underflows, we return a failure flag. if (y < optimalY) { return (0, false); } unchecked { return (y - optimalY, true); } } /// @dev Calculates the maximum amount of bonds that can be sold with the /// specified reserves. We round so that the max sell amount is /// underestimated. /// @param z The share reserves. /// @param zeta The share adjustment. /// @param y The bond reserves. /// @param zMin The minimum share reserves. /// @param t The time elapsed since the term's start. /// @param c The vault share price. /// @param mu The initial vault share price. /// @return The maximum amount of bonds that can be sold. /// @return A flag indicating whether or not the calculation was successful. function calculateMaxSellBondsInSafe( uint256 z, int256 zeta, uint256 y, uint256 zMin, uint256 t, uint256 c, uint256 mu ) internal pure returns (uint256, bool) { // If the share adjustment is negative, the minimum share reserves is // given by `zMin - zeta`, which ensures that the share reserves never // fall below the minimum share reserves. Otherwise, the minimum share // reserves is just zMin. if (zeta < 0) { zMin = zMin + uint256(-zeta); } // We solve for the maximum bond amount using the constraint that the // pool's share reserves can never fall below the minimum share reserves // `zMin`. Substituting `ze = zMin` simplifies YieldSpace to: // // k = (c / mu) * (mu * zMin) ** (1 - tau) + y' ** (1 - tau) // // This gives us the maximum bonds that can be sold to the pool as: // // y' = (k - (c / mu) * (mu * zMin) ** (1 - tau)) ** (1 / (1 - tau)). (uint256 ze, bool success) = HyperdriveMath .calculateEffectiveShareReservesSafe(z, zeta); if (!success) { return (0, false); } uint256 k = kDown(ze, y, t, c, mu); uint256 rhs = c.mulDivUp(mu.mulUp(zMin).pow(t), mu); if (k < rhs) { return (0, false); } uint256 optimalY; unchecked { optimalY = k - rhs; } if (optimalY >= ONE) { // Rounding the exponent down results in a smaller outcome. optimalY = optimalY.pow(ONE.divDown(t)); } else { // Rounding the exponent up results in a smaller outcome. optimalY = optimalY.pow(ONE.divUp(t)); } // The optimal trade size is given by dy = y' - y. If this subtraction // will underflow, we return a failure flag. if (optimalY < y) { return (0, false); } unchecked { return (optimalY - y, true); } } /// @dev Calculates the YieldSpace invariant k. This invariant is given by: /// /// k = (c / µ) * (µ * ze)^(1 - t) + y^(1 - t) /// /// This variant of the calculation overestimates the result. /// @param ze The effective share reserves. /// @param y The bond reserves. /// @param t The time elapsed since the term's start. /// @param c The vault share price. /// @param mu The initial vault share price. /// @return The YieldSpace invariant, k. function kUp( uint256 ze, uint256 y, uint256 t, uint256 c, uint256 mu ) internal pure returns (uint256) { // NOTE: Rounding up to overestimate the result. // /// k = (c / µ) * (µ * ze)^(1 - t) + y^(1 - t) return c.mulDivUp(mu.mulUp(ze).pow(t), mu) + y.pow(t); } /// @dev Calculates the YieldSpace invariant k. This invariant is given by: /// /// k = (c / µ) * (µ * ze)^(1 - t) + y^(1 - t) /// /// This variant of the calculation underestimates the result. /// @param ze The effective share reserves. /// @param y The bond reserves. /// @param t The time elapsed since the term's start. /// @param c The vault share price. /// @param mu The initial vault share price. /// @return The modified YieldSpace Constant. function kDown( uint256 ze, uint256 y, uint256 t, uint256 c, uint256 mu ) internal pure returns (uint256) { // NOTE: Rounding down to underestimate the result. // /// k = (c / µ) * (µ * ze)^(1 - t) + y^(1 - t) return c.mulDivDown(mu.mulDown(ze).pow(t), mu) + y.pow(t); } }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity >=0.5.0; import {MarketParams, Market} from "./IMorpho.sol"; /// @title IIrm /// @author Morpho Labs /// @custom:contact [email protected] /// @notice Interface that Interest Rate Models (IRMs) used by Morpho must implement. interface IIrm { /// @notice Returns the borrow rate per second (scaled by WAD) of the market `marketParams`. /// @dev Assumes that `market` corresponds to `marketParams`. function borrowRate(MarketParams memory marketParams, Market memory market) external returns (uint256); /// @notice Returns the borrow rate per second (scaled by WAD) of the market `marketParams` without modifying any /// storage. /// @dev Assumes that `market` corresponds to `marketParams`. function borrowRateView(MarketParams memory marketParams, Market memory market) external view returns (uint256); }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity >=0.5.0; type Id is bytes32; struct MarketParams { address loanToken; address collateralToken; address oracle; address irm; uint256 lltv; } /// @dev Warning: For `feeRecipient`, `supplyShares` does not contain the accrued shares since the last interest /// accrual. struct Position { uint256 supplyShares; uint128 borrowShares; uint128 collateral; } /// @dev Warning: `totalSupplyAssets` does not contain the accrued interest since the last interest accrual. /// @dev Warning: `totalBorrowAssets` does not contain the accrued interest since the last interest accrual. /// @dev Warning: `totalSupplyShares` does not contain the additional shares accrued by `feeRecipient` since the last /// interest accrual. struct Market { uint128 totalSupplyAssets; uint128 totalSupplyShares; uint128 totalBorrowAssets; uint128 totalBorrowShares; uint128 lastUpdate; uint128 fee; } struct Authorization { address authorizer; address authorized; bool isAuthorized; uint256 nonce; uint256 deadline; } struct Signature { uint8 v; bytes32 r; bytes32 s; } /// @dev This interface is used for factorizing IMorphoStaticTyping and IMorpho. /// @dev Consider using the IMorpho interface instead of this one. interface IMorphoBase { /// @notice The EIP-712 domain separator. /// @dev Warning: Every EIP-712 signed message based on this domain separator can be reused on another chain sharing /// the same chain id because the domain separator would be the same. function DOMAIN_SEPARATOR() external view returns (bytes32); /// @notice The owner of the contract. /// @dev It has the power to change the owner. /// @dev It has the power to set fees on markets and set the fee recipient. /// @dev It has the power to enable but not disable IRMs and LLTVs. function owner() external view returns (address); /// @notice The fee recipient of all markets. /// @dev The recipient receives the fees of a given market through a supply position on that market. function feeRecipient() external view returns (address); /// @notice Whether the `irm` is enabled. function isIrmEnabled(address irm) external view returns (bool); /// @notice Whether the `lltv` is enabled. function isLltvEnabled(uint256 lltv) external view returns (bool); /// @notice Whether `authorized` is authorized to modify `authorizer`'s position on all markets. /// @dev Anyone is authorized to modify their own positions, regardless of this variable. function isAuthorized(address authorizer, address authorized) external view returns (bool); /// @notice The `authorizer`'s current nonce. Used to prevent replay attacks with EIP-712 signatures. function nonce(address authorizer) external view returns (uint256); /// @notice Sets `newOwner` as `owner` of the contract. /// @dev Warning: No two-step transfer ownership. /// @dev Warning: The owner can be set to the zero address. function setOwner(address newOwner) external; /// @notice Enables `irm` as a possible IRM for market creation. /// @dev Warning: It is not possible to disable an IRM. function enableIrm(address irm) external; /// @notice Enables `lltv` as a possible LLTV for market creation. /// @dev Warning: It is not possible to disable a LLTV. function enableLltv(uint256 lltv) external; /// @notice Sets the `newFee` for the given market `marketParams`. /// @param newFee The new fee, scaled by WAD. /// @dev Warning: The recipient can be the zero address. function setFee(MarketParams memory marketParams, uint256 newFee) external; /// @notice Sets `newFeeRecipient` as `feeRecipient` of the fee. /// @dev Warning: If the fee recipient is set to the zero address, fees will accrue there and will be lost. /// @dev Modifying the fee recipient will allow the new recipient to claim any pending fees not yet accrued. To /// ensure that the current recipient receives all due fees, accrue interest manually prior to making any changes. function setFeeRecipient(address newFeeRecipient) external; /// @notice Creates the market `marketParams`. /// @dev Here is the list of assumptions on the market's dependencies (tokens, IRM and oracle) that guarantees /// Morpho behaves as expected: /// - The token should be ERC-20 compliant, except that it can omit return values on `transfer` and `transferFrom`. /// - The token balance of Morpho should only decrease on `transfer` and `transferFrom`. In particular, tokens with /// burn functions are not supported. /// - The token should not re-enter Morpho on `transfer` nor `transferFrom`. /// - The token balance of the sender (resp. receiver) should decrease (resp. increase) by exactly the given amount /// on `transfer` and `transferFrom`. In particular, tokens with fees on transfer are not supported. /// - The IRM should not re-enter Morpho. /// - The oracle should return a price with the correct scaling. /// @dev Here is a list of properties on the market's dependencies that could break Morpho's liveness properties /// (funds could get stuck): /// - The token can revert on `transfer` and `transferFrom` for a reason other than an approval or balance issue. /// - A very high amount of assets (~1e35) supplied or borrowed can make the computation of `toSharesUp` and /// `toSharesDown` overflow. /// - The IRM can revert on `borrowRate`. /// - A very high borrow rate returned by the IRM can make the computation of `interest` in `_accrueInterest` /// overflow. /// - The oracle can revert on `price`. Note that this can be used to prevent `borrow`, `withdrawCollateral` and /// `liquidate` from being used under certain market conditions. /// - A very high price returned by the oracle can make the computation of `maxBorrow` in `_isHealthy` overflow, or /// the computation of `assetsRepaid` in `liquidate` overflow. /// @dev The borrow share price of a market with less than 1e4 assets borrowed can be decreased by manipulations, to /// the point where `totalBorrowShares` is very large and borrowing overflows. function createMarket(MarketParams memory marketParams) external; /// @notice Supplies `assets` or `shares` on behalf of `onBehalf`, optionally calling back the caller's /// `onMorphoSupply` function with the given `data`. /// @dev Either `assets` or `shares` should be zero. Most use cases should rely on `assets` as an input so the /// caller is guaranteed to have `assets` tokens pulled from their balance, but the possibility to mint a specific /// amount of shares is given for full compatibility and precision. /// @dev Supplying a large amount can revert for overflow. /// @dev Supplying an amount of shares may lead to supply more or fewer assets than expected due to slippage. /// Consider using the `assets` parameter to avoid this. /// @param marketParams The market to supply assets to. /// @param assets The amount of assets to supply. /// @param shares The amount of shares to mint. /// @param onBehalf The address that will own the increased supply position. /// @param data Arbitrary data to pass to the `onMorphoSupply` callback. Pass empty data if not needed. /// @return assetsSupplied The amount of assets supplied. /// @return sharesSupplied The amount of shares minted. function supply( MarketParams memory marketParams, uint256 assets, uint256 shares, address onBehalf, bytes memory data ) external returns (uint256 assetsSupplied, uint256 sharesSupplied); /// @notice Withdraws `assets` or `shares` on behalf of `onBehalf` and sends the assets to `receiver`. /// @dev Either `assets` or `shares` should be zero. To withdraw max, pass the `shares`'s balance of `onBehalf`. /// @dev `msg.sender` must be authorized to manage `onBehalf`'s positions. /// @dev Withdrawing an amount corresponding to more shares than supplied will revert for underflow. /// @dev It is advised to use the `shares` input when withdrawing the full position to avoid reverts due to /// conversion roundings between shares and assets. /// @param marketParams The market to withdraw assets from. /// @param assets The amount of assets to withdraw. /// @param shares The amount of shares to burn. /// @param onBehalf The address of the owner of the supply position. /// @param receiver The address that will receive the withdrawn assets. /// @return assetsWithdrawn The amount of assets withdrawn. /// @return sharesWithdrawn The amount of shares burned. function withdraw( MarketParams memory marketParams, uint256 assets, uint256 shares, address onBehalf, address receiver ) external returns (uint256 assetsWithdrawn, uint256 sharesWithdrawn); /// @notice Borrows `assets` or `shares` on behalf of `onBehalf` and sends the assets to `receiver`. /// @dev Either `assets` or `shares` should be zero. Most use cases should rely on `assets` as an input so the /// caller is guaranteed to borrow `assets` of tokens, but the possibility to mint a specific amount of shares is /// given for full compatibility and precision. /// @dev `msg.sender` must be authorized to manage `onBehalf`'s positions. /// @dev Borrowing a large amount can revert for overflow. /// @dev Borrowing an amount of shares may lead to borrow fewer assets than expected due to slippage. /// Consider using the `assets` parameter to avoid this. /// @param marketParams The market to borrow assets from. /// @param assets The amount of assets to borrow. /// @param shares The amount of shares to mint. /// @param onBehalf The address that will own the increased borrow position. /// @param receiver The address that will receive the borrowed assets. /// @return assetsBorrowed The amount of assets borrowed. /// @return sharesBorrowed The amount of shares minted. function borrow( MarketParams memory marketParams, uint256 assets, uint256 shares, address onBehalf, address receiver ) external returns (uint256 assetsBorrowed, uint256 sharesBorrowed); /// @notice Repays `assets` or `shares` on behalf of `onBehalf`, optionally calling back the caller's /// `onMorphoReplay` function with the given `data`. /// @dev Either `assets` or `shares` should be zero. To repay max, pass the `shares`'s balance of `onBehalf`. /// @dev Repaying an amount corresponding to more shares than borrowed will revert for underflow. /// @dev It is advised to use the `shares` input when repaying the full position to avoid reverts due to conversion /// roundings between shares and assets. /// @dev An attacker can front-run a repay with a small repay making the transaction revert for underflow. /// @param marketParams The market to repay assets to. /// @param assets The amount of assets to repay. /// @param shares The amount of shares to burn. /// @param onBehalf The address of the owner of the debt position. /// @param data Arbitrary data to pass to the `onMorphoRepay` callback. Pass empty data if not needed. /// @return assetsRepaid The amount of assets repaid. /// @return sharesRepaid The amount of shares burned. function repay( MarketParams memory marketParams, uint256 assets, uint256 shares, address onBehalf, bytes memory data ) external returns (uint256 assetsRepaid, uint256 sharesRepaid); /// @notice Supplies `assets` of collateral on behalf of `onBehalf`, optionally calling back the caller's /// `onMorphoSupplyCollateral` function with the given `data`. /// @dev Interest are not accrued since it's not required and it saves gas. /// @dev Supplying a large amount can revert for overflow. /// @param marketParams The market to supply collateral to. /// @param assets The amount of collateral to supply. /// @param onBehalf The address that will own the increased collateral position. /// @param data Arbitrary data to pass to the `onMorphoSupplyCollateral` callback. Pass empty data if not needed. function supplyCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, bytes memory data) external; /// @notice Withdraws `assets` of collateral on behalf of `onBehalf` and sends the assets to `receiver`. /// @dev `msg.sender` must be authorized to manage `onBehalf`'s positions. /// @dev Withdrawing an amount corresponding to more collateral than supplied will revert for underflow. /// @param marketParams The market to withdraw collateral from. /// @param assets The amount of collateral to withdraw. /// @param onBehalf The address of the owner of the collateral position. /// @param receiver The address that will receive the collateral assets. function withdrawCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, address receiver) external; /// @notice Liquidates the given `repaidShares` of debt asset or seize the given `seizedAssets` of collateral on the /// given market `marketParams` of the given `borrower`'s position, optionally calling back the caller's /// `onMorphoLiquidate` function with the given `data`. /// @dev Either `seizedAssets` or `repaidShares` should be zero. /// @dev Seizing more than the collateral balance will underflow and revert without any error message. /// @dev Repaying more than the borrow balance will underflow and revert without any error message. /// @dev An attacker can front-run a liquidation with a small repay making the transaction revert for underflow. /// @param marketParams The market of the position. /// @param borrower The owner of the position. /// @param seizedAssets The amount of collateral to seize. /// @param repaidShares The amount of shares to repay. /// @param data Arbitrary data to pass to the `onMorphoLiquidate` callback. Pass empty data if not needed. /// @return The amount of assets seized. /// @return The amount of assets repaid. function liquidate( MarketParams memory marketParams, address borrower, uint256 seizedAssets, uint256 repaidShares, bytes memory data ) external returns (uint256, uint256); /// @notice Executes a flash loan. /// @dev Flash loans have access to the whole balance of the contract (the liquidity and deposited collateral of all /// markets combined, plus donations). /// @dev Warning: Not ERC-3156 compliant but compatibility is easily reached: /// - `flashFee` is zero. /// - `maxFlashLoan` is the token's balance of this contract. /// - The receiver of `assets` is the caller. /// @param token The token to flash loan. /// @param assets The amount of assets to flash loan. /// @param data Arbitrary data to pass to the `onMorphoFlashLoan` callback. function flashLoan(address token, uint256 assets, bytes calldata data) external; /// @notice Sets the authorization for `authorized` to manage `msg.sender`'s positions. /// @param authorized The authorized address. /// @param newIsAuthorized The new authorization status. function setAuthorization(address authorized, bool newIsAuthorized) external; /// @notice Sets the authorization for `authorization.authorized` to manage `authorization.authorizer`'s positions. /// @dev Warning: Reverts if the signature has already been submitted. /// @dev The signature is malleable, but it has no impact on the security here. /// @dev The nonce is passed as argument to be able to revert with a different error message. /// @param authorization The `Authorization` struct. /// @param signature The signature. function setAuthorizationWithSig(Authorization calldata authorization, Signature calldata signature) external; /// @notice Accrues interest for the given market `marketParams`. function accrueInterest(MarketParams memory marketParams) external; /// @notice Returns the data stored on the different `slots`. function extSloads(bytes32[] memory slots) external view returns (bytes32[] memory); } /// @dev This interface is inherited by Morpho so that function signatures are checked by the compiler. /// @dev Consider using the IMorpho interface instead of this one. interface IMorphoStaticTyping is IMorphoBase { /// @notice The state of the position of `user` on the market corresponding to `id`. /// @dev Warning: For `feeRecipient`, `supplyShares` does not contain the accrued shares since the last interest /// accrual. function position(Id id, address user) external view returns (uint256 supplyShares, uint128 borrowShares, uint128 collateral); /// @notice The state of the market corresponding to `id`. /// @dev Warning: `totalSupplyAssets` does not contain the accrued interest since the last interest accrual. /// @dev Warning: `totalBorrowAssets` does not contain the accrued interest since the last interest accrual. /// @dev Warning: `totalSupplyShares` does not contain the accrued shares by `feeRecipient` since the last interest /// accrual. function market(Id id) external view returns ( uint128 totalSupplyAssets, uint128 totalSupplyShares, uint128 totalBorrowAssets, uint128 totalBorrowShares, uint128 lastUpdate, uint128 fee ); /// @notice The market params corresponding to `id`. /// @dev This mapping is not used in Morpho. It is there to enable reducing the cost associated to calldata on layer /// 2s by creating a wrapper contract with functions that take `id` as input instead of `marketParams`. function idToMarketParams(Id id) external view returns (address loanToken, address collateralToken, address oracle, address irm, uint256 lltv); } /// @title IMorpho /// @author Morpho Labs /// @custom:contact [email protected] /// @dev Use this interface for Morpho to have access to all the functions with the appropriate function signatures. interface IMorpho is IMorphoBase { /// @notice The state of the position of `user` on the market corresponding to `id`. /// @dev Warning: For `feeRecipient`, `p.supplyShares` does not contain the accrued shares since the last interest /// accrual. function position(Id id, address user) external view returns (Position memory p); /// @notice The state of the market corresponding to `id`. /// @dev Warning: `m.totalSupplyAssets` does not contain the accrued interest since the last interest accrual. /// @dev Warning: `m.totalBorrowAssets` does not contain the accrued interest since the last interest accrual. /// @dev Warning: `m.totalSupplyShares` does not contain the accrued shares by `feeRecipient` since the last /// interest accrual. function market(Id id) external view returns (Market memory m); /// @notice The market params corresponding to `id`. /// @dev This mapping is not used in Morpho. It is there to enable reducing the cost associated to calldata on layer /// 2s by creating a wrapper contract with functions that take `id` as input instead of `marketParams`. function idToMarketParams(Id id) external view returns (MarketParams memory); }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; /// @title ErrorsLib /// @author Morpho Labs /// @custom:contact [email protected] /// @notice Library exposing error messages. library ErrorsLib { /// @notice Thrown when the caller is not the owner. string internal constant NOT_OWNER = "not owner"; /// @notice Thrown when the LLTV to enable exceeds the maximum LLTV. string internal constant MAX_LLTV_EXCEEDED = "max LLTV exceeded"; /// @notice Thrown when the fee to set exceeds the maximum fee. string internal constant MAX_FEE_EXCEEDED = "max fee exceeded"; /// @notice Thrown when the value is already set. string internal constant ALREADY_SET = "already set"; /// @notice Thrown when the IRM is not enabled at market creation. string internal constant IRM_NOT_ENABLED = "IRM not enabled"; /// @notice Thrown when the LLTV is not enabled at market creation. string internal constant LLTV_NOT_ENABLED = "LLTV not enabled"; /// @notice Thrown when the market is already created. string internal constant MARKET_ALREADY_CREATED = "market already created"; /// @notice Thrown when a token to transfer doesn't have code. string internal constant NO_CODE = "no code"; /// @notice Thrown when the market is not created. string internal constant MARKET_NOT_CREATED = "market not created"; /// @notice Thrown when not exactly one of the input amount is zero. string internal constant INCONSISTENT_INPUT = "inconsistent input"; /// @notice Thrown when zero assets is passed as input. string internal constant ZERO_ASSETS = "zero assets"; /// @notice Thrown when a zero address is passed as input. string internal constant ZERO_ADDRESS = "zero address"; /// @notice Thrown when the caller is not authorized to conduct an action. string internal constant UNAUTHORIZED = "unauthorized"; /// @notice Thrown when the collateral is insufficient to `borrow` or `withdrawCollateral`. string internal constant INSUFFICIENT_COLLATERAL = "insufficient collateral"; /// @notice Thrown when the liquidity is insufficient to `withdraw` or `borrow`. string internal constant INSUFFICIENT_LIQUIDITY = "insufficient liquidity"; /// @notice Thrown when the position to liquidate is healthy. string internal constant HEALTHY_POSITION = "position is healthy"; /// @notice Thrown when the authorization signature is invalid. string internal constant INVALID_SIGNATURE = "invalid signature"; /// @notice Thrown when the authorization signature is expired. string internal constant SIGNATURE_EXPIRED = "signature expired"; /// @notice Thrown when the nonce is invalid. string internal constant INVALID_NONCE = "invalid nonce"; /// @notice Thrown when a token transfer reverted. string internal constant TRANSFER_REVERTED = "transfer reverted"; /// @notice Thrown when a token transfer returned false. string internal constant TRANSFER_RETURNED_FALSE = "transfer returned false"; /// @notice Thrown when a token transferFrom reverted. string internal constant TRANSFER_FROM_REVERTED = "transferFrom reverted"; /// @notice Thrown when a token transferFrom returned false string internal constant TRANSFER_FROM_RETURNED_FALSE = "transferFrom returned false"; /// @notice Thrown when the maximum uint128 is exceeded. string internal constant MAX_UINT128_EXCEEDED = "max uint128 exceeded"; }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; import {Id, MarketParams} from "../interfaces/IMorpho.sol"; /// @title MarketParamsLib /// @author Morpho Labs /// @custom:contact [email protected] /// @notice Library to convert a market to its id. library MarketParamsLib { /// @notice The length of the data used to compute the id of a market. /// @dev The length is 5 * 32 because `MarketParams` has 5 variables of 32 bytes each. uint256 internal constant MARKET_PARAMS_BYTES_LENGTH = 5 * 32; /// @notice Returns the id of the market `marketParams`. function id(MarketParams memory marketParams) internal pure returns (Id marketParamsId) { assembly ("memory-safe") { marketParamsId := keccak256(marketParams, MARKET_PARAMS_BYTES_LENGTH) } } }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; uint256 constant WAD = 1e18; /// @title MathLib /// @author Morpho Labs /// @custom:contact [email protected] /// @notice Library to manage fixed-point arithmetic. library MathLib { /// @dev Returns (`x` * `y`) / `WAD` rounded down. function wMulDown(uint256 x, uint256 y) internal pure returns (uint256) { return mulDivDown(x, y, WAD); } /// @dev Returns (`x` * `WAD`) / `y` rounded down. function wDivDown(uint256 x, uint256 y) internal pure returns (uint256) { return mulDivDown(x, WAD, y); } /// @dev Returns (`x` * `WAD`) / `y` rounded up. function wDivUp(uint256 x, uint256 y) internal pure returns (uint256) { return mulDivUp(x, WAD, y); } /// @dev Returns (`x` * `y`) / `d` rounded down. function mulDivDown(uint256 x, uint256 y, uint256 d) internal pure returns (uint256) { return (x * y) / d; } /// @dev Returns (`x` * `y`) / `d` rounded up. function mulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256) { return (x * y + (d - 1)) / d; } /// @dev Returns the sum of the first three non-zero terms of a Taylor expansion of e^(nx) - 1, to approximate a /// continuous compound interest rate. function wTaylorCompounded(uint256 x, uint256 n) internal pure returns (uint256) { uint256 firstTerm = x * n; uint256 secondTerm = mulDivDown(firstTerm, firstTerm, 2 * WAD); uint256 thirdTerm = mulDivDown(secondTerm, firstTerm, 3 * WAD); return firstTerm + secondTerm + thirdTerm; } }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; import {Id, MarketParams, Market, IMorpho} from "../../interfaces/IMorpho.sol"; import {IIrm} from "../../interfaces/IIrm.sol"; import {MathLib} from "../MathLib.sol"; import {UtilsLib} from "../UtilsLib.sol"; import {MorphoLib} from "./MorphoLib.sol"; import {SharesMathLib} from "../SharesMathLib.sol"; import {MarketParamsLib} from "../MarketParamsLib.sol"; /// @title MorphoBalancesLib /// @author Morpho Labs /// @custom:contact [email protected] /// @notice Helper library exposing getters with the expected value after interest accrual. /// @dev This library is not used in Morpho itself and is intended to be used by integrators. /// @dev The getter to retrieve the expected total borrow shares is not exposed because interest accrual does not apply /// to it. The value can be queried directly on Morpho using `totalBorrowShares`. library MorphoBalancesLib { using MathLib for uint256; using MathLib for uint128; using UtilsLib for uint256; using MorphoLib for IMorpho; using SharesMathLib for uint256; using MarketParamsLib for MarketParams; /// @notice Returns the expected market balances of a market after having accrued interest. /// @return The expected total supply assets. /// @return The expected total supply shares. /// @return The expected total borrow assets. /// @return The expected total borrow shares. function expectedMarketBalances(IMorpho morpho, MarketParams memory marketParams) internal view returns (uint256, uint256, uint256, uint256) { Id id = marketParams.id(); Market memory market = morpho.market(id); uint256 elapsed = block.timestamp - market.lastUpdate; // Skipped if elapsed == 0 or totalBorrowAssets == 0 because interest would be null, or if irm == address(0). if (elapsed != 0 && market.totalBorrowAssets != 0 && marketParams.irm != address(0)) { uint256 borrowRate = IIrm(marketParams.irm).borrowRateView(marketParams, market); uint256 interest = market.totalBorrowAssets.wMulDown(borrowRate.wTaylorCompounded(elapsed)); market.totalBorrowAssets += interest.toUint128(); market.totalSupplyAssets += interest.toUint128(); if (market.fee != 0) { uint256 feeAmount = interest.wMulDown(market.fee); // The fee amount is subtracted from the total supply in this calculation to compensate for the fact // that total supply is already updated. uint256 feeShares = feeAmount.toSharesDown(market.totalSupplyAssets - feeAmount, market.totalSupplyShares); market.totalSupplyShares += feeShares.toUint128(); } } return (market.totalSupplyAssets, market.totalSupplyShares, market.totalBorrowAssets, market.totalBorrowShares); } /// @notice Returns the expected total supply assets of a market after having accrued interest. function expectedTotalSupplyAssets(IMorpho morpho, MarketParams memory marketParams) internal view returns (uint256 totalSupplyAssets) { (totalSupplyAssets,,,) = expectedMarketBalances(morpho, marketParams); } /// @notice Returns the expected total borrow assets of a market after having accrued interest. function expectedTotalBorrowAssets(IMorpho morpho, MarketParams memory marketParams) internal view returns (uint256 totalBorrowAssets) { (,, totalBorrowAssets,) = expectedMarketBalances(morpho, marketParams); } /// @notice Returns the expected total supply shares of a market after having accrued interest. function expectedTotalSupplyShares(IMorpho morpho, MarketParams memory marketParams) internal view returns (uint256 totalSupplyShares) { (, totalSupplyShares,,) = expectedMarketBalances(morpho, marketParams); } /// @notice Returns the expected supply assets balance of `user` on a market after having accrued interest. /// @dev Warning: Wrong for `feeRecipient` because their supply shares increase is not taken into account. /// @dev Warning: Withdrawing using the expected supply assets can lead to a revert due to conversion roundings from /// assets to shares. function expectedSupplyAssets(IMorpho morpho, MarketParams memory marketParams, address user) internal view returns (uint256) { Id id = marketParams.id(); uint256 supplyShares = morpho.supplyShares(id, user); (uint256 totalSupplyAssets, uint256 totalSupplyShares,,) = expectedMarketBalances(morpho, marketParams); return supplyShares.toAssetsDown(totalSupplyAssets, totalSupplyShares); } /// @notice Returns the expected borrow assets balance of `user` on a market after having accrued interest. /// @dev Warning: The expected balance is rounded up, so it may be greater than the market's expected total borrow /// assets. function expectedBorrowAssets(IMorpho morpho, MarketParams memory marketParams, address user) internal view returns (uint256) { Id id = marketParams.id(); uint256 borrowShares = morpho.borrowShares(id, user); (,, uint256 totalBorrowAssets, uint256 totalBorrowShares) = expectedMarketBalances(morpho, marketParams); return borrowShares.toAssetsUp(totalBorrowAssets, totalBorrowShares); } }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; import {IMorpho, Id} from "../../interfaces/IMorpho.sol"; import {MorphoStorageLib} from "./MorphoStorageLib.sol"; /// @title MorphoLib /// @author Morpho Labs /// @custom:contact [email protected] /// @notice Helper library to access Morpho storage variables. /// @dev Warning: Supply and borrow getters may return outdated values that do not include accrued interest. library MorphoLib { function supplyShares(IMorpho morpho, Id id, address user) internal view returns (uint256) { bytes32[] memory slot = _array(MorphoStorageLib.positionSupplySharesSlot(id, user)); return uint256(morpho.extSloads(slot)[0]); } function borrowShares(IMorpho morpho, Id id, address user) internal view returns (uint256) { bytes32[] memory slot = _array(MorphoStorageLib.positionBorrowSharesAndCollateralSlot(id, user)); return uint128(uint256(morpho.extSloads(slot)[0])); } function collateral(IMorpho morpho, Id id, address user) internal view returns (uint256) { bytes32[] memory slot = _array(MorphoStorageLib.positionBorrowSharesAndCollateralSlot(id, user)); return uint256(morpho.extSloads(slot)[0] >> 128); } function totalSupplyAssets(IMorpho morpho, Id id) internal view returns (uint256) { bytes32[] memory slot = _array(MorphoStorageLib.marketTotalSupplyAssetsAndSharesSlot(id)); return uint128(uint256(morpho.extSloads(slot)[0])); } function totalSupplyShares(IMorpho morpho, Id id) internal view returns (uint256) { bytes32[] memory slot = _array(MorphoStorageLib.marketTotalSupplyAssetsAndSharesSlot(id)); return uint256(morpho.extSloads(slot)[0] >> 128); } function totalBorrowAssets(IMorpho morpho, Id id) internal view returns (uint256) { bytes32[] memory slot = _array(MorphoStorageLib.marketTotalBorrowAssetsAndSharesSlot(id)); return uint128(uint256(morpho.extSloads(slot)[0])); } function totalBorrowShares(IMorpho morpho, Id id) internal view returns (uint256) { bytes32[] memory slot = _array(MorphoStorageLib.marketTotalBorrowAssetsAndSharesSlot(id)); return uint256(morpho.extSloads(slot)[0] >> 128); } function lastUpdate(IMorpho morpho, Id id) internal view returns (uint256) { bytes32[] memory slot = _array(MorphoStorageLib.marketLastUpdateAndFeeSlot(id)); return uint128(uint256(morpho.extSloads(slot)[0])); } function fee(IMorpho morpho, Id id) internal view returns (uint256) { bytes32[] memory slot = _array(MorphoStorageLib.marketLastUpdateAndFeeSlot(id)); return uint256(morpho.extSloads(slot)[0] >> 128); } function _array(bytes32 x) private pure returns (bytes32[] memory) { bytes32[] memory res = new bytes32[](1); res[0] = x; return res; } }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; import {Id} from "../../interfaces/IMorpho.sol"; /// @title MorphoStorageLib /// @author Morpho Labs /// @custom:contact [email protected] /// @notice Helper library exposing getters to access Morpho storage variables' slot. /// @dev This library is not used in Morpho itself and is intended to be used by integrators. library MorphoStorageLib { /* SLOTS */ uint256 internal constant OWNER_SLOT = 0; uint256 internal constant FEE_RECIPIENT_SLOT = 1; uint256 internal constant POSITION_SLOT = 2; uint256 internal constant MARKET_SLOT = 3; uint256 internal constant IS_IRM_ENABLED_SLOT = 4; uint256 internal constant IS_LLTV_ENABLED_SLOT = 5; uint256 internal constant IS_AUTHORIZED_SLOT = 6; uint256 internal constant NONCE_SLOT = 7; uint256 internal constant ID_TO_MARKET_PARAMS_SLOT = 8; /* SLOT OFFSETS */ uint256 internal constant LOAN_TOKEN_OFFSET = 0; uint256 internal constant COLLATERAL_TOKEN_OFFSET = 1; uint256 internal constant ORACLE_OFFSET = 2; uint256 internal constant IRM_OFFSET = 3; uint256 internal constant LLTV_OFFSET = 4; uint256 internal constant SUPPLY_SHARES_OFFSET = 0; uint256 internal constant BORROW_SHARES_AND_COLLATERAL_OFFSET = 1; uint256 internal constant TOTAL_SUPPLY_ASSETS_AND_SHARES_OFFSET = 0; uint256 internal constant TOTAL_BORROW_ASSETS_AND_SHARES_OFFSET = 1; uint256 internal constant LAST_UPDATE_AND_FEE_OFFSET = 2; /* GETTERS */ function ownerSlot() internal pure returns (bytes32) { return bytes32(OWNER_SLOT); } function feeRecipientSlot() internal pure returns (bytes32) { return bytes32(FEE_RECIPIENT_SLOT); } function positionSupplySharesSlot(Id id, address user) internal pure returns (bytes32) { return bytes32( uint256(keccak256(abi.encode(user, keccak256(abi.encode(id, POSITION_SLOT))))) + SUPPLY_SHARES_OFFSET ); } function positionBorrowSharesAndCollateralSlot(Id id, address user) internal pure returns (bytes32) { return bytes32( uint256(keccak256(abi.encode(user, keccak256(abi.encode(id, POSITION_SLOT))))) + BORROW_SHARES_AND_COLLATERAL_OFFSET ); } function marketTotalSupplyAssetsAndSharesSlot(Id id) internal pure returns (bytes32) { return bytes32(uint256(keccak256(abi.encode(id, MARKET_SLOT))) + TOTAL_SUPPLY_ASSETS_AND_SHARES_OFFSET); } function marketTotalBorrowAssetsAndSharesSlot(Id id) internal pure returns (bytes32) { return bytes32(uint256(keccak256(abi.encode(id, MARKET_SLOT))) + TOTAL_BORROW_ASSETS_AND_SHARES_OFFSET); } function marketLastUpdateAndFeeSlot(Id id) internal pure returns (bytes32) { return bytes32(uint256(keccak256(abi.encode(id, MARKET_SLOT))) + LAST_UPDATE_AND_FEE_OFFSET); } function isIrmEnabledSlot(address irm) internal pure returns (bytes32) { return keccak256(abi.encode(irm, IS_IRM_ENABLED_SLOT)); } function isLltvEnabledSlot(uint256 lltv) internal pure returns (bytes32) { return keccak256(abi.encode(lltv, IS_LLTV_ENABLED_SLOT)); } function isAuthorizedSlot(address authorizer, address authorizee) internal pure returns (bytes32) { return keccak256(abi.encode(authorizee, keccak256(abi.encode(authorizer, IS_AUTHORIZED_SLOT)))); } function nonceSlot(address authorizer) internal pure returns (bytes32) { return keccak256(abi.encode(authorizer, NONCE_SLOT)); } function idToLoanTokenSlot(Id id) internal pure returns (bytes32) { return bytes32(uint256(keccak256(abi.encode(id, ID_TO_MARKET_PARAMS_SLOT))) + LOAN_TOKEN_OFFSET); } function idToCollateralTokenSlot(Id id) internal pure returns (bytes32) { return bytes32(uint256(keccak256(abi.encode(id, ID_TO_MARKET_PARAMS_SLOT))) + COLLATERAL_TOKEN_OFFSET); } function idToOracleSlot(Id id) internal pure returns (bytes32) { return bytes32(uint256(keccak256(abi.encode(id, ID_TO_MARKET_PARAMS_SLOT))) + ORACLE_OFFSET); } function idToIrmSlot(Id id) internal pure returns (bytes32) { return bytes32(uint256(keccak256(abi.encode(id, ID_TO_MARKET_PARAMS_SLOT))) + IRM_OFFSET); } function idToLltvSlot(Id id) internal pure returns (bytes32) { return bytes32(uint256(keccak256(abi.encode(id, ID_TO_MARKET_PARAMS_SLOT))) + LLTV_OFFSET); } }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; import {MathLib} from "./MathLib.sol"; /// @title SharesMathLib /// @author Morpho Labs /// @custom:contact [email protected] /// @notice Shares management library. /// @dev This implementation mitigates share price manipulations, using OpenZeppelin's method of virtual shares: /// https://docs.openzeppelin.com/contracts/4.x/erc4626#inflation-attack. library SharesMathLib { using MathLib for uint256; /// @dev The number of virtual shares has been chosen low enough to prevent overflows, and high enough to ensure /// high precision computations. /// @dev Virtual shares can never be redeemed for the assets they are entitled to, but it is assumed the share price /// stays low enough not to inflate these assets to a significant value. /// @dev Warning: The assets to which virtual borrow shares are entitled behave like unrealizable bad debt. uint256 internal constant VIRTUAL_SHARES = 1e6; /// @dev A number of virtual assets of 1 enforces a conversion rate between shares and assets when a market is /// empty. uint256 internal constant VIRTUAL_ASSETS = 1; /// @dev Calculates the value of `assets` quoted in shares, rounding down. function toSharesDown(uint256 assets, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) { return assets.mulDivDown(totalShares + VIRTUAL_SHARES, totalAssets + VIRTUAL_ASSETS); } /// @dev Calculates the value of `shares` quoted in assets, rounding down. function toAssetsDown(uint256 shares, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) { return shares.mulDivDown(totalAssets + VIRTUAL_ASSETS, totalShares + VIRTUAL_SHARES); } /// @dev Calculates the value of `assets` quoted in shares, rounding up. function toSharesUp(uint256 assets, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) { return assets.mulDivUp(totalShares + VIRTUAL_SHARES, totalAssets + VIRTUAL_ASSETS); } /// @dev Calculates the value of `shares` quoted in assets, rounding up. function toAssetsUp(uint256 shares, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) { return shares.mulDivUp(totalAssets + VIRTUAL_ASSETS, totalShares + VIRTUAL_SHARES); } }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; import {ErrorsLib} from "../libraries/ErrorsLib.sol"; /// @title UtilsLib /// @author Morpho Labs /// @custom:contact [email protected] /// @notice Library exposing helpers. /// @dev Inspired by https://github.com/morpho-org/morpho-utils. library UtilsLib { /// @dev Returns true if there is exactly one zero among `x` and `y`. function exactlyOneZero(uint256 x, uint256 y) internal pure returns (bool z) { assembly { z := xor(iszero(x), iszero(y)) } } /// @dev Returns the min of `x` and `y`. function min(uint256 x, uint256 y) internal pure returns (uint256 z) { assembly { z := xor(x, mul(xor(x, y), lt(y, x))) } } /// @dev Returns `x` safely cast to uint128. function toUint128(uint256 x) internal pure returns (uint128) { require(x <= type(uint128).max, ErrorsLib.MAX_UINT128_EXCEEDED); return uint128(x); } /// @dev Returns max(0, x - y). function zeroFloorSub(uint256 x, uint256 y) internal pure returns (uint256 z) { assembly { z := mul(gt(x, y), sub(x, y)) } } }
// SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity >=0.7.6; library ExcessivelySafeCall { uint256 constant LOW_28_MASK = 0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff; /// @notice Use when you _really_ really _really_ don't trust the called /// contract. This prevents the called contract from causing reversion of /// the caller in as many ways as we can. /// @dev The main difference between this and a solidity low-level call is /// that we limit the number of bytes that the callee can cause to be /// copied to caller memory. This prevents stupid things like malicious /// contracts returning 10,000,000 bytes causing a local OOG when copying /// to memory. /// @param _target The address to call /// @param _gas The amount of gas to forward to the remote contract /// @param _value The value in wei to send to the remote contract /// @param _maxCopy The maximum number of bytes of returndata to copy /// to memory. /// @param _calldata The data to send to the remote contract /// @return success and returndata, as `.call()`. Returndata is capped to /// `_maxCopy` bytes. function excessivelySafeCall( address _target, uint256 _gas, uint256 _value, uint16 _maxCopy, bytes memory _calldata ) internal returns (bool, bytes memory) { // set up for assembly call uint256 _toCopy; bool _success; bytes memory _returnData = new bytes(_maxCopy); // dispatch message to recipient // by assembly calling "handle" function // we call via assembly to avoid memcopying a very large returndata // returned by a malicious contract assembly { _success := call( _gas, // gas _target, // recipient _value, // ether value add(_calldata, 0x20), // inloc mload(_calldata), // inlen 0, // outloc 0 // outlen ) // limit our copy to 256 bytes _toCopy := returndatasize() if gt(_toCopy, _maxCopy) { _toCopy := _maxCopy } // Store the length of the copied bytes mstore(_returnData, _toCopy) // copy the bytes from returndata[0:_toCopy] returndatacopy(add(_returnData, 0x20), 0, _toCopy) } return (_success, _returnData); } /// @notice Use when you _really_ really _really_ don't trust the called /// contract. This prevents the called contract from causing reversion of /// the caller in as many ways as we can. /// @dev The main difference between this and a solidity low-level call is /// that we limit the number of bytes that the callee can cause to be /// copied to caller memory. This prevents stupid things like malicious /// contracts returning 10,000,000 bytes causing a local OOG when copying /// to memory. /// @param _target The address to call /// @param _gas The amount of gas to forward to the remote contract /// @param _maxCopy The maximum number of bytes of returndata to copy /// to memory. /// @param _calldata The data to send to the remote contract /// @return success and returndata, as `.call()`. Returndata is capped to /// `_maxCopy` bytes. function excessivelySafeStaticCall( address _target, uint256 _gas, uint16 _maxCopy, bytes memory _calldata ) internal view returns (bool, bytes memory) { // set up for assembly call uint256 _toCopy; bool _success; bytes memory _returnData = new bytes(_maxCopy); // dispatch message to recipient // by assembly calling "handle" function // we call via assembly to avoid memcopying a very large returndata // returned by a malicious contract assembly { _success := staticcall( _gas, // gas _target, // recipient add(_calldata, 0x20), // inloc mload(_calldata), // inlen 0, // outloc 0 // outlen ) // limit our copy to 256 bytes _toCopy := returndatasize() if gt(_toCopy, _maxCopy) { _toCopy := _maxCopy } // Store the length of the copied bytes mstore(_returnData, _toCopy) // copy the bytes from returndata[0:_toCopy] returndatacopy(add(_returnData, 0x20), 0, _toCopy) } return (_success, _returnData); } /** * @notice Swaps function selectors in encoded contract calls * @dev Allows reuse of encoded calldata for functions with identical * argument types but different names. It simply swaps out the first 4 bytes * for the new selector. This function modifies memory in place, and should * only be used with caution. * @param _newSelector The new 4-byte selector * @param _buf The encoded contract args */ function swapSelector(bytes4 _newSelector, bytes memory _buf) internal pure { require(_buf.length >= 4); uint256 _mask = LOW_28_MASK; assembly { // load the first word of let _word := mload(add(_buf, 0x20)) // mask out the top 4 bytes // /x _word := and(_word, _mask) _word := or(_newSelector, _word) mstore(add(_buf, 0x20), _word) } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (interfaces/draft-IERC6093.sol) pragma solidity ^0.8.20; /** * @dev Standard ERC20 Errors * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC20 tokens. */ interface IERC20Errors { /** * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers. * @param sender Address whose tokens are being transferred. * @param balance Current balance for the interacting account. * @param needed Minimum amount required to perform a transfer. */ error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed); /** * @dev Indicates a failure with the token `sender`. Used in transfers. * @param sender Address whose tokens are being transferred. */ error ERC20InvalidSender(address sender); /** * @dev Indicates a failure with the token `receiver`. Used in transfers. * @param receiver Address to which tokens are being transferred. */ error ERC20InvalidReceiver(address receiver); /** * @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers. * @param spender Address that may be allowed to operate on tokens without being their owner. * @param allowance Amount of tokens a `spender` is allowed to operate with. * @param needed Minimum amount required to perform a transfer. */ error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed); /** * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. * @param approver Address initiating an approval operation. */ error ERC20InvalidApprover(address approver); /** * @dev Indicates a failure with the `spender` to be approved. Used in approvals. * @param spender Address that may be allowed to operate on tokens without being their owner. */ error ERC20InvalidSpender(address spender); } /** * @dev Standard ERC721 Errors * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC721 tokens. */ interface IERC721Errors { /** * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in EIP-20. * Used in balance queries. * @param owner Address of the current owner of a token. */ error ERC721InvalidOwner(address owner); /** * @dev Indicates a `tokenId` whose `owner` is the zero address. * @param tokenId Identifier number of a token. */ error ERC721NonexistentToken(uint256 tokenId); /** * @dev Indicates an error related to the ownership over a particular token. Used in transfers. * @param sender Address whose tokens are being transferred. * @param tokenId Identifier number of a token. * @param owner Address of the current owner of a token. */ error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner); /** * @dev Indicates a failure with the token `sender`. Used in transfers. * @param sender Address whose tokens are being transferred. */ error ERC721InvalidSender(address sender); /** * @dev Indicates a failure with the token `receiver`. Used in transfers. * @param receiver Address to which tokens are being transferred. */ error ERC721InvalidReceiver(address receiver); /** * @dev Indicates a failure with the `operator`’s approval. Used in transfers. * @param operator Address that may be allowed to operate on tokens without being their owner. * @param tokenId Identifier number of a token. */ error ERC721InsufficientApproval(address operator, uint256 tokenId); /** * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. * @param approver Address initiating an approval operation. */ error ERC721InvalidApprover(address approver); /** * @dev Indicates a failure with the `operator` to be approved. Used in approvals. * @param operator Address that may be allowed to operate on tokens without being their owner. */ error ERC721InvalidOperator(address operator); } /** * @dev Standard ERC1155 Errors * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC1155 tokens. */ interface IERC1155Errors { /** * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers. * @param sender Address whose tokens are being transferred. * @param balance Current balance for the interacting account. * @param needed Minimum amount required to perform a transfer. * @param tokenId Identifier number of a token. */ error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId); /** * @dev Indicates a failure with the token `sender`. Used in transfers. * @param sender Address whose tokens are being transferred. */ error ERC1155InvalidSender(address sender); /** * @dev Indicates a failure with the token `receiver`. Used in transfers. * @param receiver Address to which tokens are being transferred. */ error ERC1155InvalidReceiver(address receiver); /** * @dev Indicates a failure with the `operator`’s approval. Used in transfers. * @param operator Address that may be allowed to operate on tokens without being their owner. * @param owner Address of the current owner of a token. */ error ERC1155MissingApprovalForAll(address operator, address owner); /** * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. * @param approver Address initiating an approval operation. */ error ERC1155InvalidApprover(address approver); /** * @dev Indicates a failure with the `operator` to be approved. Used in approvals. * @param operator Address that may be allowed to operate on tokens without being their owner. */ error ERC1155InvalidOperator(address operator); /** * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation. * Used in batch transfers. * @param idsLength Length of the array of token identifiers * @param valuesLength Length of the array of token amounts */ error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/ERC20.sol) pragma solidity ^0.8.20; import {IERC20} from "./IERC20.sol"; import {IERC20Metadata} from "./extensions/IERC20Metadata.sol"; import {Context} from "../../utils/Context.sol"; import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol"; /** * @dev Implementation of the {IERC20} interface. * * This implementation is agnostic to the way tokens are created. This means * that a supply mechanism has to be added in a derived contract using {_mint}. * * TIP: For a detailed writeup see our guide * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How * to implement supply mechanisms]. * * The default value of {decimals} is 18. To change this, you should override * this function so it returns a different value. * * We have followed general OpenZeppelin Contracts guidelines: functions revert * instead returning `false` on failure. This behavior is nonetheless * conventional and does not conflict with the expectations of ERC20 * applications. * * Additionally, an {Approval} event is emitted on calls to {transferFrom}. * This allows applications to reconstruct the allowance for all accounts just * by listening to said events. Other implementations of the EIP may not emit * these events, as it isn't required by the specification. */ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { mapping(address account => uint256) private _balances; mapping(address account => mapping(address spender => uint256)) private _allowances; uint256 private _totalSupply; string private _name; string private _symbol; /** * @dev Sets the values for {name} and {symbol}. * * All two of these values are immutable: they can only be set once during * construction. */ constructor(string memory name_, string memory symbol_) { _name = name_; _symbol = symbol_; } /** * @dev Returns the name of the token. */ function name() public view virtual returns (string memory) { return _name; } /** * @dev Returns the symbol of the token, usually a shorter version of the * name. */ function symbol() public view virtual returns (string memory) { return _symbol; } /** * @dev Returns the number of decimals used to get its user representation. * For example, if `decimals` equals `2`, a balance of `505` tokens should * be displayed to a user as `5.05` (`505 / 10 ** 2`). * * Tokens usually opt for a value of 18, imitating the relationship between * Ether and Wei. This is the default value returned by this function, unless * it's overridden. * * NOTE: This information is only used for _display_ purposes: it in * no way affects any of the arithmetic of the contract, including * {IERC20-balanceOf} and {IERC20-transfer}. */ function decimals() public view virtual returns (uint8) { return 18; } /** * @dev See {IERC20-totalSupply}. */ function totalSupply() public view virtual returns (uint256) { return _totalSupply; } /** * @dev See {IERC20-balanceOf}. */ function balanceOf(address account) public view virtual returns (uint256) { return _balances[account]; } /** * @dev See {IERC20-transfer}. * * Requirements: * * - `to` cannot be the zero address. * - the caller must have a balance of at least `value`. */ function transfer(address to, uint256 value) public virtual returns (bool) { address owner = _msgSender(); _transfer(owner, to, value); return true; } /** * @dev See {IERC20-allowance}. */ function allowance(address owner, address spender) public view virtual returns (uint256) { return _allowances[owner][spender]; } /** * @dev See {IERC20-approve}. * * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on * `transferFrom`. This is semantically equivalent to an infinite approval. * * Requirements: * * - `spender` cannot be the zero address. */ function approve(address spender, uint256 value) public virtual returns (bool) { address owner = _msgSender(); _approve(owner, spender, value); return true; } /** * @dev See {IERC20-transferFrom}. * * Emits an {Approval} event indicating the updated allowance. This is not * required by the EIP. See the note at the beginning of {ERC20}. * * NOTE: Does not update the allowance if the current allowance * is the maximum `uint256`. * * Requirements: * * - `from` and `to` cannot be the zero address. * - `from` must have a balance of at least `value`. * - the caller must have allowance for ``from``'s tokens of at least * `value`. */ function transferFrom(address from, address to, uint256 value) public virtual returns (bool) { address spender = _msgSender(); _spendAllowance(from, spender, value); _transfer(from, to, value); return true; } /** * @dev Moves a `value` amount of tokens from `from` to `to`. * * This internal function is equivalent to {transfer}, and can be used to * e.g. implement automatic token fees, slashing mechanisms, etc. * * Emits a {Transfer} event. * * NOTE: This function is not virtual, {_update} should be overridden instead. */ function _transfer(address from, address to, uint256 value) internal { if (from == address(0)) { revert ERC20InvalidSender(address(0)); } if (to == address(0)) { revert ERC20InvalidReceiver(address(0)); } _update(from, to, value); } /** * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from` * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding * this function. * * Emits a {Transfer} event. */ function _update(address from, address to, uint256 value) internal virtual { if (from == address(0)) { // Overflow check required: The rest of the code assumes that totalSupply never overflows _totalSupply += value; } else { uint256 fromBalance = _balances[from]; if (fromBalance < value) { revert ERC20InsufficientBalance(from, fromBalance, value); } unchecked { // Overflow not possible: value <= fromBalance <= totalSupply. _balances[from] = fromBalance - value; } } if (to == address(0)) { unchecked { // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply. _totalSupply -= value; } } else { unchecked { // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256. _balances[to] += value; } } emit Transfer(from, to, value); } /** * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0). * Relies on the `_update` mechanism * * Emits a {Transfer} event with `from` set to the zero address. * * NOTE: This function is not virtual, {_update} should be overridden instead. */ function _mint(address account, uint256 value) internal { if (account == address(0)) { revert ERC20InvalidReceiver(address(0)); } _update(address(0), account, value); } /** * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply. * Relies on the `_update` mechanism. * * Emits a {Transfer} event with `to` set to the zero address. * * NOTE: This function is not virtual, {_update} should be overridden instead */ function _burn(address account, uint256 value) internal { if (account == address(0)) { revert ERC20InvalidSender(address(0)); } _update(account, address(0), value); } /** * @dev Sets `value` as the allowance of `spender` over the `owner` s tokens. * * This internal function is equivalent to `approve`, and can be used to * e.g. set automatic allowances for certain subsystems, etc. * * Emits an {Approval} event. * * Requirements: * * - `owner` cannot be the zero address. * - `spender` cannot be the zero address. * * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument. */ function _approve(address owner, address spender, uint256 value) internal { _approve(owner, spender, value, true); } /** * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event. * * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by * `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any * `Approval` event during `transferFrom` operations. * * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to * true using the following override: * ``` * function _approve(address owner, address spender, uint256 value, bool) internal virtual override { * super._approve(owner, spender, value, true); * } * ``` * * Requirements are the same as {_approve}. */ function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual { if (owner == address(0)) { revert ERC20InvalidApprover(address(0)); } if (spender == address(0)) { revert ERC20InvalidSpender(address(0)); } _allowances[owner][spender] = value; if (emitEvent) { emit Approval(owner, spender, value); } } /** * @dev Updates `owner` s allowance for `spender` based on spent `value`. * * Does not update the allowance value in case of infinite allowance. * Revert if not enough allowance is available. * * Does not emit an {Approval} event. */ function _spendAllowance(address owner, address spender, uint256 value) internal virtual { uint256 currentAllowance = allowance(owner, spender); if (currentAllowance != type(uint256).max) { if (currentAllowance < value) { revert ERC20InsufficientAllowance(spender, currentAllowance, value); } unchecked { _approve(owner, spender, currentAllowance - value, false); } } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Metadata.sol) pragma solidity ^0.8.20; import {IERC20} from "../IERC20.sol"; /** * @dev Interface for the optional metadata functions from the ERC20 standard. */ interface IERC20Metadata is IERC20 { /** * @dev Returns the name of the token. */ function name() external view returns (string memory); /** * @dev Returns the symbol of the token. */ function symbol() external view returns (string memory); /** * @dev Returns the decimals places of the token. */ function decimals() external view returns (uint8); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol) pragma solidity ^0.8.20; /** * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. * * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't * need to send a transaction, and thus is not required to hold Ether at all. * * ==== Security Considerations * * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be * considered as an intention to spend the allowance in any specific way. The second is that because permits have * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be * generally recommended is: * * ```solidity * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public { * try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {} * doThing(..., value); * } * * function doThing(..., uint256 value) public { * token.safeTransferFrom(msg.sender, address(this), value); * ... * } * ``` * * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also * {SafeERC20-safeTransferFrom}). * * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so * contracts should have entry points that don't rely on permit. */ interface IERC20Permit { /** * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, * given ``owner``'s signed approval. * * IMPORTANT: The same issues {IERC20-approve} has related to transaction * ordering also apply here. * * Emits an {Approval} event. * * Requirements: * * - `spender` cannot be the zero address. * - `deadline` must be a timestamp in the future. * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` * over the EIP712-formatted function arguments. * - the signature must use ``owner``'s current nonce (see {nonces}). * * For more information on the signature format, see the * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP * section]. * * CAUTION: See Security Considerations above. */ function permit( address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) external; /** * @dev Returns the current nonce for `owner`. This value must be * included whenever a signature is generated for {permit}. * * Every successful call to {permit} increases ``owner``'s nonce by one. This * prevents a signature from being used multiple times. */ function nonces(address owner) external view returns (uint256); /** * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. */ // solhint-disable-next-line func-name-mixedcase function DOMAIN_SEPARATOR() external view returns (bytes32); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol) pragma solidity ^0.8.20; /** * @dev Interface of the ERC20 standard as defined in the EIP. */ interface IERC20 { /** * @dev Emitted when `value` tokens are moved from one account (`from`) to * another (`to`). * * Note that `value` may be zero. */ event Transfer(address indexed from, address indexed to, uint256 value); /** * @dev Emitted when the allowance of a `spender` for an `owner` is set by * a call to {approve}. `value` is the new allowance. */ event Approval(address indexed owner, address indexed spender, uint256 value); /** * @dev Returns the value of tokens in existence. */ function totalSupply() external view returns (uint256); /** * @dev Returns the value of tokens owned by `account`. */ function balanceOf(address account) external view returns (uint256); /** * @dev Moves a `value` amount of tokens from the caller's account to `to`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transfer(address to, uint256 value) external returns (bool); /** * @dev Returns the remaining number of tokens that `spender` will be * allowed to spend on behalf of `owner` through {transferFrom}. This is * zero by default. * * This value changes when {approve} or {transferFrom} are called. */ function allowance(address owner, address spender) external view returns (uint256); /** * @dev Sets a `value` amount of tokens as the allowance of `spender` over the * caller's tokens. * * Returns a boolean value indicating whether the operation succeeded. * * IMPORTANT: Beware that changing an allowance with this method brings the risk * that someone may use both the old and the new allowance by unfortunate * transaction ordering. One possible solution to mitigate this race * condition is to first reduce the spender's allowance to 0 and set the * desired value afterwards: * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * * Emits an {Approval} event. */ function approve(address spender, uint256 value) external returns (bool); /** * @dev Moves a `value` amount of tokens from `from` to `to` using the * allowance mechanism. `value` is then deducted from the caller's * allowance. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transferFrom(address from, address to, uint256 value) external returns (bool); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol) pragma solidity ^0.8.20; import {IERC20} from "../IERC20.sol"; import {IERC20Permit} from "../extensions/IERC20Permit.sol"; import {Address} from "../../../utils/Address.sol"; /** * @title SafeERC20 * @dev Wrappers around ERC20 operations that throw on failure (when the token * contract returns false). Tokens that return no value (and instead revert or * throw on failure) are also supported, non-reverting calls are assumed to be * successful. * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. */ library SafeERC20 { using Address for address; /** * @dev An operation with an ERC20 token failed. */ error SafeERC20FailedOperation(address token); /** * @dev Indicates a failed `decreaseAllowance` request. */ error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease); /** * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value, * non-reverting calls are assumed to be successful. */ function safeTransfer(IERC20 token, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value))); } /** * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful. */ function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value))); } /** * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value, * non-reverting calls are assumed to be successful. */ function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { uint256 oldAllowance = token.allowance(address(this), spender); forceApprove(token, spender, oldAllowance + value); } /** * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no * value, non-reverting calls are assumed to be successful. */ function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal { unchecked { uint256 currentAllowance = token.allowance(address(this), spender); if (currentAllowance < requestedDecrease) { revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease); } forceApprove(token, spender, currentAllowance - requestedDecrease); } } /** * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value, * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval * to be set to zero before setting it to a non-zero value, such as USDT. */ function forceApprove(IERC20 token, address spender, uint256 value) internal { bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value)); if (!_callOptionalReturnBool(token, approvalCall)) { _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0))); _callOptionalReturn(token, approvalCall); } } /** * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement * on the return value: the return value is optional (but if data is returned, it must not be false). * @param token The token targeted by the call. * @param data The call data (encoded using abi.encode or one of its variants). */ function _callOptionalReturn(IERC20 token, bytes memory data) private { // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that // the target address contains contract code and also asserts for success in the low-level call. bytes memory returndata = address(token).functionCall(data); if (returndata.length != 0 && !abi.decode(returndata, (bool))) { revert SafeERC20FailedOperation(address(token)); } } /** * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement * on the return value: the return value is optional (but if data is returned, it must not be false). * @param token The token targeted by the call. * @param data The call data (encoded using abi.encode or one of its variants). * * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead. */ function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) { // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false // and not revert is the subcall reverts. (bool success, bytes memory returndata) = address(token).call(data); return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0; } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol) pragma solidity ^0.8.20; /** * @dev Collection of functions related to the address type */ library Address { /** * @dev The ETH balance of the account is not enough to perform the operation. */ error AddressInsufficientBalance(address account); /** * @dev There's no code at `target` (it is not a contract). */ error AddressEmptyCode(address target); /** * @dev A call to an address target failed. The target may have reverted. */ error FailedInnerCall(); /** * @dev Replacement for Solidity's `transfer`: sends `amount` wei to * `recipient`, forwarding all available gas and reverting on errors. * * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost * of certain opcodes, possibly making contracts go over the 2300 gas limit * imposed by `transfer`, making them unable to receive funds via * `transfer`. {sendValue} removes this limitation. * * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more]. * * IMPORTANT: because control is transferred to `recipient`, care must be * taken to not create reentrancy vulnerabilities. Consider using * {ReentrancyGuard} or the * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. */ function sendValue(address payable recipient, uint256 amount) internal { if (address(this).balance < amount) { revert AddressInsufficientBalance(address(this)); } (bool success, ) = recipient.call{value: amount}(""); if (!success) { revert FailedInnerCall(); } } /** * @dev Performs a Solidity function call using a low level `call`. A * plain `call` is an unsafe replacement for a function call: use this * function instead. * * If `target` reverts with a revert reason or custom error, it is bubbled * up by this function (like regular Solidity function calls). However, if * the call reverted with no returned reason, this function reverts with a * {FailedInnerCall} error. * * Returns the raw returned data. To convert to the expected return value, * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. * * Requirements: * * - `target` must be a contract. * - calling `target` with `data` must not revert. */ function functionCall(address target, bytes memory data) internal returns (bytes memory) { return functionCallWithValue(target, data, 0); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but also transferring `value` wei to `target`. * * Requirements: * * - the calling contract must have an ETH balance of at least `value`. * - the called Solidity function must be `payable`. */ function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { if (address(this).balance < value) { revert AddressInsufficientBalance(address(this)); } (bool success, bytes memory returndata) = target.call{value: value}(data); return verifyCallResultFromTarget(target, success, returndata); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a static call. */ function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { (bool success, bytes memory returndata) = target.staticcall(data); return verifyCallResultFromTarget(target, success, returndata); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a delegate call. */ function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { (bool success, bytes memory returndata) = target.delegatecall(data); return verifyCallResultFromTarget(target, success, returndata); } /** * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an * unsuccessful call. */ function verifyCallResultFromTarget( address target, bool success, bytes memory returndata ) internal view returns (bytes memory) { if (!success) { _revert(returndata); } else { // only check if target is a contract if the call was successful and the return data is empty // otherwise we already know that it was a contract if (returndata.length == 0 && target.code.length == 0) { revert AddressEmptyCode(target); } return returndata; } } /** * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the * revert reason or with a default {FailedInnerCall} error. */ function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) { if (!success) { _revert(returndata); } else { return returndata; } } /** * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}. */ function _revert(bytes memory returndata) private pure { // Look for revert reason and bubble it up if present if (returndata.length > 0) { // The easiest way to bubble the revert reason is using memory via assembly /// @solidity memory-safe-assembly assembly { let returndata_size := mload(returndata) revert(add(32, returndata), returndata_size) } } else { revert FailedInnerCall(); } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol) pragma solidity ^0.8.20; /** * @dev Provides information about the current execution context, including the * sender of the transaction and its data. While these are generally available * via msg.sender and msg.data, they should not be accessed in such a direct * manner, since when dealing with meta-transactions the account sending and * paying for execution may not be the actual sender (as far as an application * is concerned). * * This contract is only required for intermediate, library-like contracts. */ abstract contract Context { function _msgSender() internal view virtual returns (address) { return msg.sender; } function _msgData() internal view virtual returns (bytes calldata) { return msg.data; } function _contextSuffixLength() internal view virtual returns (uint256) { return 0; } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SignedMath.sol) pragma solidity ^0.8.20; /** * @dev Standard signed math utilities missing in the Solidity language. */ library SignedMath { /** * @dev Returns the largest of two signed numbers. */ function max(int256 a, int256 b) internal pure returns (int256) { return a > b ? a : b; } /** * @dev Returns the smallest of two signed numbers. */ function min(int256 a, int256 b) internal pure returns (int256) { return a < b ? a : b; } /** * @dev Returns the average of two signed numbers without overflow. * The result is rounded towards zero. */ function average(int256 a, int256 b) internal pure returns (int256) { // Formula from the book "Hacker's Delight" int256 x = (a & b) + ((a ^ b) >> 1); return x + (int256(uint256(x) >> 255) & (a ^ b)); } /** * @dev Returns the absolute unsigned value of a signed value. */ function abs(int256 n) internal pure returns (uint256) { unchecked { // must be unchecked in order to support `n = type(int256).min` return uint256(n >= 0 ? n : -n); } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/ReentrancyGuard.sol) pragma solidity ^0.8.20; /** * @dev Contract module that helps prevent reentrant calls to a function. * * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier * available, which can be applied to functions to make sure there are no nested * (reentrant) calls to them. * * Note that because there is a single `nonReentrant` guard, functions marked as * `nonReentrant` may not call one another. This can be worked around by making * those functions `private`, and then adding `external` `nonReentrant` entry * points to them. * * TIP: If you would like to learn more about reentrancy and alternative ways * to protect against it, check out our blog post * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. */ abstract contract ReentrancyGuard { // Booleans are more expensive than uint256 or any type that takes up a full // word because each write operation emits an extra SLOAD to first read the // slot's contents, replace the bits taken up by the boolean, and then write // back. This is the compiler's defense against contract upgrades and // pointer aliasing, and it cannot be disabled. // The values being non-zero value makes deployment a bit more expensive, // but in exchange the refund on every call to nonReentrant will be lower in // amount. Since refunds are capped to a percentage of the total // transaction's gas, it is best to keep them low in cases like this one, to // increase the likelihood of the full refund coming into effect. uint256 private constant NOT_ENTERED = 1; uint256 private constant ENTERED = 2; uint256 private _status; /** * @dev Unauthorized reentrant call. */ error ReentrancyGuardReentrantCall(); constructor() { _status = NOT_ENTERED; } /** * @dev Prevents a contract from calling itself, directly or indirectly. * Calling a `nonReentrant` function from another `nonReentrant` * function is not supported. It is possible to prevent this from happening * by making the `nonReentrant` function external, and making it call a * `private` function that does the actual work. */ modifier nonReentrant() { _nonReentrantBefore(); _; _nonReentrantAfter(); } function _nonReentrantBefore() private { // On the first call to nonReentrant, _status will be NOT_ENTERED if (_status == ENTERED) { revert ReentrancyGuardReentrantCall(); } // Any calls to nonReentrant after this point will fail _status = ENTERED; } function _nonReentrantAfter() private { // By storing the original value once again, a refund is triggered (see // https://eips.ethereum.org/EIPS/eip-2200) _status = NOT_ENTERED; } /** * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a * `nonReentrant` function in the call stack. */ function _reentrancyGuardEntered() internal view returns (bool) { return _status == ENTERED; } }
{ "viaIR": false, "optimizer": { "enabled": true, "runs": 13000 }, "evmVersion": "paris", "metadata": { "useLiteralContent": true }, "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "devdoc", "userdoc", "metadata", "abi" ] } }, "libraries": { "contracts/src/libraries/LPMath.sol": { "LPMath": "0xdf5d682404b0611f46f2626d9d5a37eb6a6fd27d" }, "contracts/src/instances/morpho-blue/MorphoBlueConversions.sol": { "MorphoBlueConversions": "0x1a4cee4e32ea51ec7671a0fd7333ca64fbf004f0" } } }
Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
[{"inputs":[{"components":[{"internalType":"contract IERC20","name":"baseToken","type":"address"},{"internalType":"contract IERC20","name":"vaultSharesToken","type":"address"},{"internalType":"address","name":"linkerFactory","type":"address"},{"internalType":"bytes32","name":"linkerCodeHash","type":"bytes32"},{"internalType":"uint256","name":"initialVaultSharePrice","type":"uint256"},{"internalType":"uint256","name":"minimumShareReserves","type":"uint256"},{"internalType":"uint256","name":"minimumTransactionAmount","type":"uint256"},{"internalType":"uint256","name":"circuitBreakerDelta","type":"uint256"},{"internalType":"uint256","name":"positionDuration","type":"uint256"},{"internalType":"uint256","name":"checkpointDuration","type":"uint256"},{"internalType":"uint256","name":"timeStretch","type":"uint256"},{"internalType":"address","name":"governance","type":"address"},{"internalType":"address","name":"feeCollector","type":"address"},{"internalType":"address","name":"sweepCollector","type":"address"},{"internalType":"address","name":"checkpointRewarder","type":"address"},{"components":[{"internalType":"uint256","name":"curve","type":"uint256"},{"internalType":"uint256","name":"flat","type":"uint256"},{"internalType":"uint256","name":"governanceLP","type":"uint256"},{"internalType":"uint256","name":"governanceZombie","type":"uint256"}],"internalType":"struct IHyperdrive.Fees","name":"fees","type":"tuple"}],"internalType":"struct IHyperdrive.PoolConfig","name":"_config","type":"tuple"},{"internalType":"contract IHyperdriveAdminController","name":"_adminController","type":"address"},{"internalType":"bytes","name":"_extraData","type":"bytes"},{"internalType":"bytes32","name":"_salt","type":"bytes32"}],"name":"deployTarget","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"nonpayable","type":"function"}]
Contract Creation Code
608060405234801561001057600080fd5b50614d6a806100206000396000f3fe60806040523480156200001157600080fd5b50600436106200002e5760003560e01c80638c98b3c61462000033575b600080fd5b6200004a62000044366004620002c4565b62000073565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b600080838060200190518101906200008c919062000439565b6040805133602082015290810185905290915060600160405160208183030381529060405280519060200120868683604051620000c99062000105565b620000d793929190620004d6565b8190604051809103906000f5905080158015620000f8573d6000803e3d6000fd5b509150505b949350505050565b6146b3806200068283390190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051610200810167ffffffffffffffff8111828210171562000169576200016962000113565b60405290565b73ffffffffffffffffffffffffffffffffffffffff811681146200019257600080fd5b50565b8035620001a2816200016f565b919050565b600060808284031215620001ba57600080fd5b6040516080810181811067ffffffffffffffff82111715620001e057620001e062000113565b8060405250809150823581526020830135602082015260408301356040820152606083013560608201525092915050565b600082601f8301126200022357600080fd5b813567ffffffffffffffff8082111562000241576200024162000113565b604051601f83017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f011681019082821181831017156200028a576200028a62000113565b81604052838152866020858801011115620002a457600080fd5b836020870160208301376000602085830101528094505050505092915050565b6000806000808486036102c0811215620002dd57600080fd5b61026080821215620002ee57600080fd5b620002f862000142565b9150620003058762000195565b8252620003156020880162000195565b6020830152620003286040880162000195565b6040830152606087013560608301526080870135608083015260a087013560a083015260c087013560c083015260e087013560e08301526101008088013581840152506101208088013581840152506101408088013581840152506101606200039381890162000195565b90830152610180620003a788820162000195565b908301526101a0620003bb88820162000195565b908301526101c0620003cf88820162000195565b908301526101e0620003e489898301620001a7565b8184015250819550620003f981880162000195565b9450505061028085013567ffffffffffffffff8111156200041957600080fd5b620004278782880162000211565b949793965093946102a0013593505050565b600060a082840312156200044c57600080fd5b60405160a0810181811067ffffffffffffffff8211171562000472576200047262000113565b604052825162000482816200016f565b8152602083015162000494816200016f565b60208201526040830151620004a9816200016f565b60408201526060830151620004be816200016f565b60608201526080928301519281019290925250919050565b835173ffffffffffffffffffffffffffffffffffffffff168152610320810160208501516200051d602084018273ffffffffffffffffffffffffffffffffffffffff169052565b50604085015162000546604084018273ffffffffffffffffffffffffffffffffffffffff169052565b50606085015160608301526080850151608083015260a085015160a083015260c085015160c083015260e085015160e083015261010080860151818401525061012080860151818401525061014080860151818401525061016080860151620005c68285018273ffffffffffffffffffffffffffffffffffffffff169052565b50506101808581015173ffffffffffffffffffffffffffffffffffffffff908116918401919091526101a0808701518216908401526101c0808701518216908401526101e080870151805191850191909152602080820151610200860152604080830151610220870152606092830151610240870152878416610260870152865184166102808701529086015183166102a086015285015182166102c0850152840151166102e08301526080830151610300830152620000fd56fe6103206040523480156200001257600080fd5b50604051620046b3380380620046b38339810160408190526200003591620005e4565b6001600081905583516001600160a01b039081166080818152602080880151841660a0908152828901516101a0528801516101c05260c0808901516101e090815260e0808b015161020052610120808c0151909352610100808c0151909152610140808c0151909152908a0180515190925281518301519052805160409081015161016052905160609081015161018052818a0151861661024052808a01516102605285891661022052875186166102808190529288015186166102a0529087015185166102c0528601519093166102e0528401516103005283926200011c929062000126565b505050506200078a565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b0390811663095ea7b360e01b17909152620001809085908390620001f216565b620001ec57604080516001600160a01b038516602482015260006044808301919091528251808303909101815260649091019091526020810180516001600160e01b0390811663095ea7b360e01b17909152620001e0918691620002a316565b620001ec8482620002a3565b50505050565b6000806000846001600160a01b03168460405162000211919062000735565b6000604051808303816000865af19150503d806000811462000250576040519150601f19603f3d011682016040523d82523d6000602084013e62000255565b606091505b5091509150818015620002835750805115806200028357508080602001905181019062000283919062000766565b80156200029a57506000856001600160a01b03163b115b95945050505050565b6000620002ba6001600160a01b0384168362000316565b90508051600014158015620002e2575080806020019051810190620002e0919062000766565b155b156200031157604051635274afe760e01b81526001600160a01b03841660048201526024015b60405180910390fd5b505050565b606062000326838360006200032d565b9392505050565b606081471015620003545760405163cd78605960e01b815230600482015260240162000308565b600080856001600160a01b0316848660405162000372919062000735565b60006040518083038185875af1925050503d8060008114620003b1576040519150601f19603f3d011682016040523d82523d6000602084013e620003b6565b606091505b509092509050620003c9868383620003d3565b9695505050505050565b606082620003ec57620003e68262000437565b62000326565b81511580156200040457506001600160a01b0384163b155b156200042f57604051639996b31560e01b81526001600160a01b038516600482015260240162000308565b508062000326565b805115620004485780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b50565b60405161020081016001600160401b03811182821017156200049657634e487b7160e01b600052604160045260246000fd5b60405290565b6001600160a01b03811681146200046157600080fd5b8051620004bf816200049c565b919050565b600060808284031215620004d757600080fd5b604051608081016001600160401b03811182821017156200050857634e487b7160e01b600052604160045260246000fd5b8060405250809150825181526020830151602082015260408301516040820152606083015160608201525092915050565b600060a082840312156200054c57600080fd5b60405160a081016001600160401b03811182821017156200057d57634e487b7160e01b600052604160045260246000fd5b8060405250809150825162000592816200049c565b81526020830151620005a4816200049c565b60208201526040830151620005b9816200049c565b60408201526060830151620005ce816200049c565b6060820152608092830151920191909152919050565b6000806000838503610320811215620005fc57600080fd5b610260808212156200060d57600080fd5b6200061762000464565b91506200062486620004b2565b82526200063460208701620004b2565b60208301526200064760408701620004b2565b6040830152606086015160608301526080860151608083015260a086015160a083015260c086015160c083015260e086015160e0830152610100808701518184015250610120808701518184015250610140808701518184015250610160620006b2818801620004b2565b90830152610180620006c6878201620004b2565b908301526101a0620006da878201620004b2565b908301526101c0620006ee878201620004b2565b908301526101e06200070388888301620004c4565b818401525081945062000718818701620004b2565b935050506200072c85610280860162000539565b90509250925092565b6000825160005b818110156200075857602081860181015185830152016200073c565b506000920191825250919050565b6000602082840312156200077957600080fd5b815180151581146200032657600080fd5b60805160a05160c05160e05161010051610120516101405161016051610180516101a0516101c0516101e05161020051610220516102405161026051610280516102a0516102c0516102e05161030051613d866200092d6000396000818161163601528181612e300152612fcf01526000818161160f01528181612e090152612f930152600081816115e701528181612de10152612f570152600081816115bf01528181612db90152612f1b01526000818161156f01528181612d690152612e98015260005050600050506000610b4f0152600050506000818161029c01526129b3015260008181611fdd015281816120400152818161248d015281816124d201528181612702015261298d015260008181610659015261296701526000611a4c01526000611c050152600081816108470152611bd90152600050506000818161067a01526129d901526000818161077601526131a5015260008181610203015281816104de0152818161056b015281816105f3015281816106f6015261072e0152600050506000818161159701528181612d910152612edf0152613d866000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c8063074a6de914610046578063414f826d14610072578063cbc1343414610087575b600080fd5b61005961005436600461378d565b61009a565b6040805192835260208301919091520160405180910390f35b6100856100803660046137e4565b6100b5565b005b61005961009536600461378d565b6100c3565b6000806100a88585856100d1565b915091505b935093915050565b6100bf82826101a3565b5050565b6000806100a8858585610286565b6000806100dc61041c565b6100e58361045f565b60006100ef6104be565b90506101066100fc6104d6565b8260046001610502565b506101143387838888610cd1565b90935091506101266020850185613828565b73ffffffffffffffffffffffffffffffffffffffff16337f07210cf9a89fae8012341fdc131255728787856379269f07c2e41c23b3c09b5884868561017160408b0160208c01613853565b61017e60408c018c613870565b6040516101909695949392919061391e565b60405180910390a3506100ad6001600055565b6101ab61041c565b60006101b56104be565b6000848152600860205260409020600101549091506fffffffffffffffffffffffffffffffff16156101f2576101eb8183610eba565b505061027c565b60006101fc6104d6565b90506102287f000000000000000000000000000000000000000000000000000000000000000085613987565b15158061023457508381105b1561026b576040517fecd29e8100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6102788483856000610502565b5050505b6100bf6001600055565b60008061029161041c565b61029a8361045f565b7f00000000000000000000000000000000000000000000000000000000000000008510156102f4576040517f423bbb4600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006102fe6104be565b905061030b6100fc6104d6565b5061031860003388611209565b61034f7f03000000000000000000000000000000000000000000000000000000000000006103496020870187613828565b88611323565b600061036a6103616020870187613828565b88848989610cd1565b909450905061037981886139ca565b92506000610386836113dd565b5090506103966020870187613828565b73ffffffffffffffffffffffffffffffffffffffff16337f1c7999deb68182de77ce89d32f82d0e13eb042921b2bfa9f35aa1c43f62f261e8a88876103e160408d0160208e01613853565b8a888e80604001906103f39190613870565b6040516104079897969594939291906139dd565b60405180910390a35050506100ad6001600055565b600260005403610458576040517f3ee5aeb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002600055565b600061046e6020830183613828565b73ffffffffffffffffffffffffffffffffffffffff16036104bb576040517ff0dd15fd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50565b60006104d1670de0b6b3a7640000611532565b905090565b60006104d1427f00000000000000000000000000000000000000000000000000000000000000006116c5565b600084815260086020526040812060018101546fffffffffffffffffffffffffffffffff1615158061053357504286115b1561055557600101546fffffffffffffffffffffffffffffffff169050610cc9565b60008060006105626104d6565b905060006105907f00000000000000000000000000000000000000000000000000000000000000008b613a25565b90505b81811015610618576000818152600860205260409020600101546fffffffffffffffffffffffffffffffff1680156105f0576000828152600860205260409020549094506fffffffffffffffffffffffffffffffff169250610618565b507f000000000000000000000000000000000000000000000000000000000000000001610593565b836000036106a15788935061069e61062e6116e2565b60025470010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff167f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000061170c565b92505b506106ab8361172c565b6001850180547fffffffffffffffffffffffffffffffff00000000000000000000000000000000166fffffffffffffffffffffffffffffffff9290921691909117905561072261071b7f00000000000000000000000000000000000000000000000000000000000000008b6139ca565b8a8461177c565b6107608961075a6107537f000000000000000000000000000000000000000000000000000000000000000083613a25565b4290611931565b8461177c565b61076988611946565b506000905060088161079b7f00000000000000000000000000000000000000000000000000000000000000008d6139ca565b815260208101919091526040016000908120600101546fffffffffffffffffffffffffffffffff1691506107d060028c611b5c565b6000818152600b60205260408120549192508c8c831561095357600192506000806107ff86898d866000611bc2565b9150915080600960008282546108159190613a25565b9091555061083190508660008461082b81611c91565b88611ced565b61083b8183613a25565b915061086b86838a8e877f0000000000000000000000000000000000000000000000000000000000000000611f14565b915061087f61087a8385611f5e565b611f73565b600680546002906108a79084906201000090046dffffffffffffffffffffffffffff16613a38565b92506101000a8154816dffffffffffffffffffffffffffff02191690836dffffffffffffffffffffffffffff1602179055506108e28261172c565b6006805460109061091a90849070010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff16613a5f565b92506101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff16021790555050505b6000610960600184611b5c565b6000818152600b60205260409020549091508015610aac576001945060008061098d838b8f886001611bc2565b9150915080600960008282546109a39190613a25565b909155506109bf9050836000846109b981611c91565b8a611fbd565b6109c981836139ca565b91506109d861087a8387611f5e565b60068054600290610a009084906201000090046dffffffffffffffffffffffffffff16613a38565b92506101000a8154816dffffffffffffffffffffffffffff02191690836dffffffffffffffffffffffffffff160217905550610a3b8261172c565b60068054601090610a7390849070010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff16613a5f565b92506101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff16021790555050505b8415610ae657610ad8610abe87611c91565b610ac783611c91565b610ad19190613a88565b6000612277565b8e610ae38482610eba565b50505b6000610af1846113dd565b50604080518e815260208101879052908101899052606081018490526080810182905290915085907fff888cf98d2696e95c8c39aa98c9ad55a5378008f7a56614c9353b7137a57ab79060a00160405180910390a260008f905060007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663f25964586040518163ffffffff1660e01b8152600401602060405180830381865afa158015610bb8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bdc9190613aa8565b905073ffffffffffffffffffffffffffffffffffffffff811615610cb757610cb45a604051336024820152604481018a905284151560648201526000908190608401604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f69114d4e0000000000000000000000000000000000000000000000000000000017905273ffffffffffffffffffffffffffffffffffffffff8616939291906122e0565b50505b509b9d50505050505050505050505050505b949350505050565b600080610cdd8561236b565b505060075485906fffffffffffffffffffffffffffffffff1680821115610d1457806fffffffffffffffffffffffffffffffff1691505b81600003610d29576000809250925050610eb0565b610d547f03000000000000000000000000000000000000000000000000000000000000008984611209565b600754600090610d909084906fffffffffffffffffffffffffffffffff7001000000000000000000000000000000009091048116908516612378565b9050610d9b8361172c565b610da59083613ac5565b600780547fffffffffffffffffffffffffffffffff00000000000000000000000000000000166fffffffffffffffffffffffffffffffff92909216919091179055610def8161172c565b60078054601090610e2790849070010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff16613ac5565b92506101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff160217905550610e688188876123b4565b9350610e74868461244d565b841015610ead576040517fc972651700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50505b9550959350505050565b6007547f03000000000000000000000000000000000000000000000000000000000000006000908152600b6020527f3ae204c42bf80d9df0ca83c69a5573417a7a5570428fcb513b3a0276db3e75435490918291610f2a916fffffffffffffffffffffffffffffffff16906139ca565b905080600003610f3e576001915050611203565b6000610f4985612462565b905080600003610f5e57600192505050611203565b600080610f6c838589612519565b9150915080610f82576000945050505050611203565b604080517f2c03ef6800000000000000000000000000000000000000000000000000000000815283518051600483015260208082015160248401528184015160448401526060808301516064850152608080840151608486015260a08085015160a487015260c08086015160c488015260e08681015160e4890152610100808801516101048a01526101208801516101248a01526101408801516101448a015261016090970151610164890152948a0151610184880152968901516101a4870152918801516101c48601528701516101e48501528601516102048401529285015161022483015291840151610244820152908301516102648201526102848101879052600090819073df5d682404b0611f46f2626d9d5a37eb6a6fd27d90632c03ef68906102a4016040805180830381865af41580156110c6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110ea9190613aee565b915091506111086110fa82611c91565b61110390613b12565b61268d565b92508261111e5760009650505050505050611203565b6111278261172c565b6007805460009061114b9084906fffffffffffffffffffffffffffffffff16613a5f565b92506101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff16021790555061118a8161172c565b600780546010906111c290849070010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff16613a5f565b92506101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff160217905550600196505050505050505b92915050565b6000838152600a6020908152604080832073ffffffffffffffffffffffffffffffffffffffff86168452909152902054811115611272576040517ff4d678b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000838152600a6020908152604080832073ffffffffffffffffffffffffffffffffffffffff86168452825280832080548590039055858352600b909152812080548392906112c29084906139ca565b9091555050604080518481526020810183905260009173ffffffffffffffffffffffffffffffffffffffff85169133917fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6291015b60405180910390a4505050565b6000838152600a6020908152604080832073ffffffffffffffffffffffffffffffffffffffff8616845290915281208054839290611362908490613a25565b90915550506000838152600b602052604081208054839290611385908490613a25565b9091555050604080518481526020810183905273ffffffffffffffffffffffffffffffffffffffff84169160009133917fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f629101611316565b60008060008073df5d682404b0611f46f2626d9d5a37eb6a6fd27d635a1b419e611406876128ac565b6040518263ffffffff1660e01b81526004016114229190613b4a565b6040805180830381865af415801561143e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114629190613bca565b9150915080611478575060009485945092505050565b600754600b6020527f3ae204c42bf80d9df0ca83c69a5573417a7a5570428fcb513b3a0276db3e75435460008080527fdf7de25b7f1fd6d0b5205f0e18f1f35bd7b8d84cce336588d184533ce43a6f765490926fffffffffffffffffffffffffffffffff16916114e791613a25565b6114f191906139ca565b90508060000361150957506000958695509350505050565b6000808711611519576000611524565b611524848884612378565b976001975095505050505050565b6040517f3c2063f000000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000811660048301527f0000000000000000000000000000000000000000000000000000000000000000811660248301527f0000000000000000000000000000000000000000000000000000000000000000811660448301527f0000000000000000000000000000000000000000000000000000000000000000811660648301527f00000000000000000000000000000000000000000000000000000000000000001660848201527f000000000000000000000000000000000000000000000000000000000000000060a482015260c48101829052600090731a4cee4e32ea51ec7671a0fd7333ca64fbf004f090633c2063f09060e4015b602060405180830381865af41580156116a1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112039190613bfa565b60006116d18284613987565b6116db90846139ca565b9392505050565b6002546004546000916104d1916fffffffffffffffffffffffffffffffff90911690600f0b612a6e565b60006117238261171d858888612378565b90612a94565b95945050505050565b60006fffffffffffffffffffffffffffffffff821115611778576040517f1e15f2a200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5090565b60008381526008602052604090205470010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff168083036117be5750505050565b6000848152600860205260408120546fffffffffffffffffffffffffffffffff1690819003611842576117f08361172c565b600086815260086020526040902080547fffffffffffffffffffffffffffffffff00000000000000000000000000000000166fffffffffffffffffffffffffffffffff929092169190911790556118e1565b61189361188e670de0b6b3a764000061185b88866139ca565b6118659190613c13565b85670de0b6b3a7640000611879878a6139ca565b6118839190613c13565b859291906001612b09565b61172c565b600086815260086020526040902080547fffffffffffffffffffffffffffffffff00000000000000000000000000000000166fffffffffffffffffffffffffffffffff929092169190911790555b6118ea8461172c565b60009586526008602052604090952080546fffffffffffffffffffffffffffffffff9687167001000000000000000000000000000000000296169590951790945550505050565b600081831161194057826116db565b50919050565b600654600090819061197f90849070010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff16611f5e565b6006546201000090046dffffffffffffffffffffffffffff169250905081811115611b575760006119b083836139ca565b90506119bf61188e8286612ba7565b600680546010906119f790849070010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff16613ac5565b92506101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff1602179055506000611a428583612bbc90919063ffffffff16565b90506000611a70827f0000000000000000000000000000000000000000000000000000000000000000611f5e565b90508060096000828254611a849190613a25565b90915550611a94905081836139ca565b9150611a9f8261172c565b60028054600090611ac39084906fffffffffffffffffffffffffffffffff16613a5f565b92506101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff160217905550611b0282612bd1565b60048054600090611b17908490600f0b613c2a565b92506101000a8154816fffffffffffffffffffffffffffffffff0219169083600f0b6fffffffffffffffffffffffffffffffff1602179055508493505050505b915091565b60007effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff821115611bb7576040517fb7d0949700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5060f89190911b1790565b600080611bcf8785612bbc565b91506000611bfd837f0000000000000000000000000000000000000000000000000000000000000000611f5e565b9050611c29817f0000000000000000000000000000000000000000000000000000000000000000611f5e565b91508315611c4c57611c3b82826139ca565b611c4590846139ca565b9250611c63565b611c5682826139ca565b611c609084613a25565b92505b86861015611c8657611c76838789612378565b9250611c83828789612378565b91505b509550959350505050565b60007f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff821115611778576040517f72dd4e0200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60045470010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff16611d6261188e82611d2f670de0b6b3a764000086613c13565b60055470010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff1691908a6000612b09565b600580546fffffffffffffffffffffffffffffffff928316700100000000000000000000000000000000029216919091179055611d9e8661172c565b611da89082613ac5565b600480546fffffffffffffffffffffffffffffffff928316700100000000000000000000000000000000029216919091179055611de48461172c565b60028054600090611e089084906fffffffffffffffffffffffffffffffff16613a5f565b92506101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff160217905550611e4783612c1d565b60048054600090611e5c908490600f0b613c2a565b92506101000a8154816fffffffffffffffffffffffffffffffff0219169083600f0b6fffffffffffffffffffffffffffffffff160217905550611e9e8561172c565b60028054601090611ed690849070010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff16613ac5565b92506101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff160217905550505050505050565b600080611f2c84611f268a888a612378565b90612bbc565b9050611f39888486612378565b611f439082613a25565b905086811115611f535786810391505b509695505050505050565b60006116db8383670de0b6b3a7640000612378565b60006dffffffffffffffffffffffffffff821115611778576040517f10d62a2e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002546fffffffffffffffffffffffffffffffff168381108061200857507f000000000000000000000000000000000000000000000000000000000000000061200685836139ca565b105b1561201557612015612c93565b6004549084900390600f0b61202a8482613a88565b90508361203686611c91565b13801561206b57507f00000000000000000000000000000000000000000000000000000000000000006120698383612a6e565b105b1561207857612078612c93565b60035470010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff166120d961188e826120ba670de0b6b3a764000088613c13565b6005546fffffffffffffffffffffffffffffffff1691908c6000612b09565b600580547fffffffffffffffffffffffffffffffff00000000000000000000000000000000166fffffffffffffffffffffffffffffffff9290921691909117905561212488826139ca565b905061212f8161172c565b600380546fffffffffffffffffffffffffffffffff92831670010000000000000000000000000000000002921691909117905561216b8361172c565b600280547fffffffffffffffffffffffffffffffff00000000000000000000000000000000166fffffffffffffffffffffffffffffffff929092169190911790556121b582612c1d565b600480547fffffffffffffffffffffffffffffffff00000000000000000000000000000000166fffffffffffffffffffffffffffffffff929092169190911790556121ff8761172c565b6002805460109061223790849070010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff16613a5f565b92506101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff1602179055505050505050505050565b60035461229b9061188e906fffffffffffffffffffffffffffffffff168484612cc5565b600380547fffffffffffffffffffffffffffffffff00000000000000000000000000000000166fffffffffffffffffffffffffffffffff929092169190911790555050565b6000606060008060008661ffff1667ffffffffffffffff81111561230657612306613c78565b6040519080825280601f01601f191660200182016040528015612330576020820181803683370190505b5090506000808751602089018b8e8ef191503d925086831115612351578692505b828152826000602083013e90999098509650505050505050565b6000611203826004610eba565b6000827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04841183021582026123ad57600080fd5b5091020490565b6000806123c18585611f5e565b90506123cc81612d2c565b9450846000036123e05760009150506116db565b8491506123f36040840160208501613853565b156124225761241b856124096020860186613828565b6124166040870187613870565b612e82565b9150612445565b612445856124336020860186613828565b6124406040870187613870565b6130e4565b509392505050565b60006116db8383670de0b6b3a7640000613116565b6003546000908190612486906fffffffffffffffffffffffffffffffff1684612ba7565b90506124b27f000000000000000000000000000000000000000000000000000000000000000082613a25565b6002546fffffffffffffffffffffffffffffffff161115611940576002547f00000000000000000000000000000000000000000000000000000000000000009061250f9083906fffffffffffffffffffffffffffffffff166139ca565b6116db91906139ca565b6125216136df565b60008061252d846128ac565b9050600073df5d682404b0611f46f2626d9d5a37eb6a6fd27d635a1b419e836040518263ffffffff1660e01b81526004016125689190613b4a565b6040805180830381865af4158015612584573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906125a89190613bca565b93509050826125bd5750600091506100ad9050565b60006125e46125df846101600151856101400151611f5e90919063ffffffff16565b611c91565b6126046125df85610120015186610100015161244d90919063ffffffff16565b61260e9190613a88565b604080516101208101825285815260208082019590955260008052600b85527fdf7de25b7f1fd6d0b5205f0e18f1f35bd7b8d84cce336588d184533ce43a6f7654818301526060810199909952608089019990995260a088015250815160c087015281015160e086015290940151610100840152509092600192509050565b600254600480546040517fd0b457ce0000000000000000000000000000000000000000000000000000000081526fffffffffffffffffffffffffffffffff808516938201849052600f9290920b60248201819052700100000000000000000000000000000000909404909116604482018190527f000000000000000000000000000000000000000000000000000000000000000060648301526084820185905260009391849081908190819073df5d682404b0611f46f2626d9d5a37eb6a6fd27d9063d0b457ce9060a401608060405180830381865af4158015612775573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906127999190613ca7565b9350935093509350806127b55750600098975050505050505050565b868414612807576127c58461172c565b600280547fffffffffffffffffffffffffffffffff00000000000000000000000000000000166fffffffffffffffffffffffffffffffff929092169190911790555b8583146128595761281783612c1d565b600480547fffffffffffffffffffffffffffffffff00000000000000000000000000000000166fffffffffffffffffffffffffffffffff929092169190911790555b84821461289d576128698261172c565b600280546fffffffffffffffffffffffffffffffff9283167001000000000000000000000000000000000292169190911790555b50600198975050505050505050565b6129106040518061018001604052806000815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b60408051610180810182526002546fffffffffffffffffffffffffffffffff8082168352600454600f0b602084015270010000000000000000000000000000000091829004811693830193909352606082018590527f000000000000000000000000000000000000000000000000000000000000000060808301527f000000000000000000000000000000000000000000000000000000000000000060a08301527f000000000000000000000000000000000000000000000000000000000000000060c08301527f000000000000000000000000000000000000000000000000000000000000000060e08301526003540482166101008201526005549091610120830191612a1e911661315a565b81526004546fffffffffffffffffffffffffffffffff7001000000000000000000000000000000009182900481166020840152600554604090930192612a669290041661315a565b905292915050565b600080612a7b84846131d0565b909250905080612a8d57612a8d612c93565b5092915050565b600081600003612aad5750670de0b6b3a7640000611203565b82600003612abd57506000611203565b6000612ac883611c91565b90506000612add612ad886611c91565b61320f565b9050818102612af4670de0b6b3a764000082613ce8565b9050612aff816134b0565b9695505050505050565b600082600003612b1a575084611723565b8115612b6d57612b4b612b2d8487613a25565b612b378587611f5e565b612b41888a611f5e565b611f269190613a25565b90506000612b598588611931565b905080821015612b67578091505b50611723565b828503612b7c57506000611723565b612aff612b8984876139ca565b612b93858761244d565b612b9d888a611f5e565b611f2691906139ca565b60006116db83670de0b6b3a764000084613116565b60006116db83670de0b6b3a764000084612378565b60006f7fffffffffffffffffffffffffffffff821115611778576040517fa5353be500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60007fffffffffffffffffffffffffffffffff80000000000000000000000000000000821280612c5c57506f7fffffffffffffffffffffffffffffff82135b15611778576040517fa5353be500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517fbb55fd2700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600080612cd38460006136c9565b612cde8460006136c9565b612ce89190613a88565b90506000811315612d0457612cfd8186613a25565b9450612d23565b6000811215612d2357612d1681613b12565b612d2090866139ca565b94505b50929392505050565b6040517f49124ca300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000811660048301527f0000000000000000000000000000000000000000000000000000000000000000811660248301527f0000000000000000000000000000000000000000000000000000000000000000811660448301527f0000000000000000000000000000000000000000000000000000000000000000811660648301527f00000000000000000000000000000000000000000000000000000000000000001660848201527f000000000000000000000000000000000000000000000000000000000000000060a482015260c48101829052600090731a4cee4e32ea51ec7671a0fd7333ca64fbf004f0906349124ca39060e401611684565b600080612e8e86611532565b905080156130db577f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16635c2bea496040518060a001604052807f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1681526020017f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1681526020017f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1681526020017f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1681526020017f0000000000000000000000000000000000000000000000000000000000000000815250612ffa89611532565b604080517fffffffff0000000000000000000000000000000000000000000000000000000060e086901b168152835173ffffffffffffffffffffffffffffffffffffffff90811660048301526020850151811660248301529184015182166044820152606084015182166064820152608090930151608484015260a4830191909152600060c48301523060e483015288166101048201526101240160408051808303816000875af11580156130b3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906130d79190613aee565b5091505b50949350505050565b6040517f6a17288200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048411830215820261314b57600080fd5b50910281810615159190040190565b600080670de0b6b3a764000061316e6104d6565b6131789190613c13565b9050808311613188576000613192565b61319281846139ca565b91506116db6131c9670de0b6b3a76400007f0000000000000000000000000000000000000000000000000000000000000000613c13565b8390612bbc565b6000806000836131df86611c91565b6131e99190613a88565b90506000811215613201576000809250925050613208565b9150600190505b9250929050565b600080821361324a576040517fe61b497500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b506fffffffffffffffffffffffffffffffff811160071b81811c67ffffffffffffffff1060061b1781811c63ffffffff1060051b1781811c61ffff1060041b1781811c60ff10600390811b90911782811c600f1060021b1782811c909110600190811b90911782811c90911017609f8181036060019290921b917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa08201906132f49084901c611c91565b6c465772b2bbbb5f824b15207a3081018102606090811d6d0388eaa27412d5aca026815d636e018202811d6d0df99ac502031bf953eff472fdcc018202811d6d13cdffb29d51d99322bdff5f2211018202811d6d0a0f742023def783a307a986912e018202811d6d01920d8043ca89b5239253284e42018202811d6c0b7a86d7375468fac667a0a527016c29508e458543d8aa4df2abee7883018302821d6d0139601a2efabe717e604cbb4894018302821d6d02247f7a7b6594320649aa03aba1018302821d7fffffffffffffffffffffffffffffffffffffff73c0c716a594e00d54e3c4cbc9018302821d7ffffffffffffffffffffffffffffffffffffffdc7b88c420e53a9890533129f6f01830290911d7fffffffffffffffffffffffffffffffffffffff465fda27eb4d63ded474e5f832019091027ffffffffffffffff5f6af8f7b3396644f18e157960000000000000000000000000105711340daa0d5f769dba1915cef59f0815a5506027d0267a36c0c95b3975ab3ee5b203a7614a3f75373f047d803ae7b6687f2b391909102017d57115e47018c7177eebf7cd370a3356a1b7863008a5ae8028c72b88642840160ae1d92915050565b60007ffffffffffffffffffffffffffffffffffffffffffffffffdb731c958f34d94c182136134e157506000919050565b680755bf798b4a1bf1e58212613523576040517f73a2d6b100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6503782dace9d9604e83901b059150600060606bb17217f7d1cf79abc9e3b39884821b056b80000000000000000000000001901d6bb17217f7d1cf79abc9e3b3988102909303927fffffffffffffffffffffffffffffffffffffffdbf3ccf1604d263450f02a550484018402606090811d6d0277594991cfc85f6e2461837cd9018502811d7fffffffffffffffffffffffffffffffffffffe5adedaa1cb095af9e4da10e363c018502811d6db1bbb201f443cf962f1a1d3db4a5018502811d7ffffffffffffffffffffffffffffffffffffd38dc772608b0ae56cce01296c0eb018502811d6e05180bb14799ab47a8a8cb2a527d57016d02d16720577bd19bf614176fe9ea6c10fe68e7fd37d0007b713f765087018702831d9081019087017ffffffffffffffffffffffffffffffffffffffe2c69812cf03b0763fd454a8f7e01810290921d6e0587f503bb6ea29d25fcb74019645001860279d835ebba824c98fb31b83b2ca45c000000000000000000000000018181059550929350909190612aff74029d9dc38563c32e5c2f6dc192ee70ef65f9978af3860260c38690031c611c91565b60008183136136d857816116db565b5090919050565b60405180610120016040528061374f6040518061018001604052806000815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b6000806000606084860312156137a257600080fd5b8335925060208401359150604084013567ffffffffffffffff8111156137c757600080fd5b8401606081870312156137d957600080fd5b809150509250925092565b600080604083850312156137f757600080fd5b50508035926020909101359150565b73ffffffffffffffffffffffffffffffffffffffff811681146104bb57600080fd5b60006020828403121561383a57600080fd5b81356116db81613806565b80151581146104bb57600080fd5b60006020828403121561386557600080fd5b81356116db81613845565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18436030181126138a557600080fd5b83018035915067ffffffffffffffff8211156138c057600080fd5b60200191503681900382131561320857600080fd5b8183528181602085013750600060208284010152600060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b868152856020820152846040820152831515606082015260a06080820152600061394c60a0830184866138d5565b98975050505050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b60008261399657613996613958565b500690565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b818103818111156112035761120361399b565b88815287602082015286604082015285151560608201528460808201528360a082015260e060c08201526000613a1760e0830184866138d5565b9a9950505050505050505050565b808201808211156112035761120361399b565b6dffffffffffffffffffffffffffff818116838216019080821115612a8d57612a8d61399b565b6fffffffffffffffffffffffffffffffff818116838216019080821115612a8d57612a8d61399b565b8181036000831280158383131683831282161715612a8d57612a8d61399b565b600060208284031215613aba57600080fd5b81516116db81613806565b6fffffffffffffffffffffffffffffffff828116828216039080821115612a8d57612a8d61399b565b60008060408385031215613b0157600080fd5b505080516020909101519092909150565b60007f80000000000000000000000000000000000000000000000000000000000000008203613b4357613b4361399b565b5060000390565b815181526020808301519082015260408083015190820152606080830151908201526080808301519082015260a0808301519082015260c0808301519082015260e0808301519082015261010080830151908201526101208083015190820152610140808301519082015261016080830151908201526101808101611203565b60008060408385031215613bdd57600080fd5b825191506020830151613bef81613845565b809150509250929050565b600060208284031215613c0c57600080fd5b5051919050565b80820281158282048414176112035761120361399b565b600f81810b9083900b016f7fffffffffffffffffffffffffffffff81137fffffffffffffffffffffffffffffffff80000000000000000000000000000000821217156112035761120361399b565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60008060008060808587031215613cbd57600080fd5b8451935060208501519250604085015191506060850151613cdd81613845565b939692955090935050565b600082613cf757613cf7613958565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff83147f800000000000000000000000000000000000000000000000000000000000000083141615613d4b57613d4b61399b565b50059056fea26469706673582212206af15b7cfc050c0a0be45100f558dec51a42edc02bab1fb9588e0920f7cb16a964736f6c63430008160033a26469706673582212200d975207cf4ac5e10a7d97e7613c96ed1ad126795d64eff9a33516407579742864736f6c63430008160033
Deployed Bytecode
0x60806040523480156200001157600080fd5b50600436106200002e5760003560e01c80638c98b3c61462000033575b600080fd5b6200004a62000044366004620002c4565b62000073565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b600080838060200190518101906200008c919062000439565b6040805133602082015290810185905290915060600160405160208183030381529060405280519060200120868683604051620000c99062000105565b620000d793929190620004d6565b8190604051809103906000f5905080158015620000f8573d6000803e3d6000fd5b509150505b949350505050565b6146b3806200068283390190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051610200810167ffffffffffffffff8111828210171562000169576200016962000113565b60405290565b73ffffffffffffffffffffffffffffffffffffffff811681146200019257600080fd5b50565b8035620001a2816200016f565b919050565b600060808284031215620001ba57600080fd5b6040516080810181811067ffffffffffffffff82111715620001e057620001e062000113565b8060405250809150823581526020830135602082015260408301356040820152606083013560608201525092915050565b600082601f8301126200022357600080fd5b813567ffffffffffffffff8082111562000241576200024162000113565b604051601f83017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f011681019082821181831017156200028a576200028a62000113565b81604052838152866020858801011115620002a457600080fd5b836020870160208301376000602085830101528094505050505092915050565b6000806000808486036102c0811215620002dd57600080fd5b61026080821215620002ee57600080fd5b620002f862000142565b9150620003058762000195565b8252620003156020880162000195565b6020830152620003286040880162000195565b6040830152606087013560608301526080870135608083015260a087013560a083015260c087013560c083015260e087013560e08301526101008088013581840152506101208088013581840152506101408088013581840152506101606200039381890162000195565b90830152610180620003a788820162000195565b908301526101a0620003bb88820162000195565b908301526101c0620003cf88820162000195565b908301526101e0620003e489898301620001a7565b8184015250819550620003f981880162000195565b9450505061028085013567ffffffffffffffff8111156200041957600080fd5b620004278782880162000211565b949793965093946102a0013593505050565b600060a082840312156200044c57600080fd5b60405160a0810181811067ffffffffffffffff8211171562000472576200047262000113565b604052825162000482816200016f565b8152602083015162000494816200016f565b60208201526040830151620004a9816200016f565b60408201526060830151620004be816200016f565b60608201526080928301519281019290925250919050565b835173ffffffffffffffffffffffffffffffffffffffff168152610320810160208501516200051d602084018273ffffffffffffffffffffffffffffffffffffffff169052565b50604085015162000546604084018273ffffffffffffffffffffffffffffffffffffffff169052565b50606085015160608301526080850151608083015260a085015160a083015260c085015160c083015260e085015160e083015261010080860151818401525061012080860151818401525061014080860151818401525061016080860151620005c68285018273ffffffffffffffffffffffffffffffffffffffff169052565b50506101808581015173ffffffffffffffffffffffffffffffffffffffff908116918401919091526101a0808701518216908401526101c0808701518216908401526101e080870151805191850191909152602080820151610200860152604080830151610220870152606092830151610240870152878416610260870152865184166102808701529086015183166102a086015285015182166102c0850152840151166102e08301526080830151610300830152620000fd56fe6103206040523480156200001257600080fd5b50604051620046b3380380620046b38339810160408190526200003591620005e4565b6001600081905583516001600160a01b039081166080818152602080880151841660a0908152828901516101a0528801516101c05260c0808901516101e090815260e0808b015161020052610120808c0151909352610100808c0151909152610140808c0151909152908a0180515190925281518301519052805160409081015161016052905160609081015161018052818a0151861661024052808a01516102605285891661022052875186166102808190529288015186166102a0529087015185166102c0528601519093166102e0528401516103005283926200011c929062000126565b505050506200078a565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b0390811663095ea7b360e01b17909152620001809085908390620001f216565b620001ec57604080516001600160a01b038516602482015260006044808301919091528251808303909101815260649091019091526020810180516001600160e01b0390811663095ea7b360e01b17909152620001e0918691620002a316565b620001ec8482620002a3565b50505050565b6000806000846001600160a01b03168460405162000211919062000735565b6000604051808303816000865af19150503d806000811462000250576040519150601f19603f3d011682016040523d82523d6000602084013e62000255565b606091505b5091509150818015620002835750805115806200028357508080602001905181019062000283919062000766565b80156200029a57506000856001600160a01b03163b115b95945050505050565b6000620002ba6001600160a01b0384168362000316565b90508051600014158015620002e2575080806020019051810190620002e0919062000766565b155b156200031157604051635274afe760e01b81526001600160a01b03841660048201526024015b60405180910390fd5b505050565b606062000326838360006200032d565b9392505050565b606081471015620003545760405163cd78605960e01b815230600482015260240162000308565b600080856001600160a01b0316848660405162000372919062000735565b60006040518083038185875af1925050503d8060008114620003b1576040519150601f19603f3d011682016040523d82523d6000602084013e620003b6565b606091505b509092509050620003c9868383620003d3565b9695505050505050565b606082620003ec57620003e68262000437565b62000326565b81511580156200040457506001600160a01b0384163b155b156200042f57604051639996b31560e01b81526001600160a01b038516600482015260240162000308565b508062000326565b805115620004485780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b50565b60405161020081016001600160401b03811182821017156200049657634e487b7160e01b600052604160045260246000fd5b60405290565b6001600160a01b03811681146200046157600080fd5b8051620004bf816200049c565b919050565b600060808284031215620004d757600080fd5b604051608081016001600160401b03811182821017156200050857634e487b7160e01b600052604160045260246000fd5b8060405250809150825181526020830151602082015260408301516040820152606083015160608201525092915050565b600060a082840312156200054c57600080fd5b60405160a081016001600160401b03811182821017156200057d57634e487b7160e01b600052604160045260246000fd5b8060405250809150825162000592816200049c565b81526020830151620005a4816200049c565b60208201526040830151620005b9816200049c565b60408201526060830151620005ce816200049c565b6060820152608092830151920191909152919050565b6000806000838503610320811215620005fc57600080fd5b610260808212156200060d57600080fd5b6200061762000464565b91506200062486620004b2565b82526200063460208701620004b2565b60208301526200064760408701620004b2565b6040830152606086015160608301526080860151608083015260a086015160a083015260c086015160c083015260e086015160e0830152610100808701518184015250610120808701518184015250610140808701518184015250610160620006b2818801620004b2565b90830152610180620006c6878201620004b2565b908301526101a0620006da878201620004b2565b908301526101c0620006ee878201620004b2565b908301526101e06200070388888301620004c4565b818401525081945062000718818701620004b2565b935050506200072c85610280860162000539565b90509250925092565b6000825160005b818110156200075857602081860181015185830152016200073c565b506000920191825250919050565b6000602082840312156200077957600080fd5b815180151581146200032657600080fd5b60805160a05160c05160e05161010051610120516101405161016051610180516101a0516101c0516101e05161020051610220516102405161026051610280516102a0516102c0516102e05161030051613d866200092d6000396000818161163601528181612e300152612fcf01526000818161160f01528181612e090152612f930152600081816115e701528181612de10152612f570152600081816115bf01528181612db90152612f1b01526000818161156f01528181612d690152612e98015260005050600050506000610b4f0152600050506000818161029c01526129b3015260008181611fdd015281816120400152818161248d015281816124d201528181612702015261298d015260008181610659015261296701526000611a4c01526000611c050152600081816108470152611bd90152600050506000818161067a01526129d901526000818161077601526131a5015260008181610203015281816104de0152818161056b015281816105f3015281816106f6015261072e0152600050506000818161159701528181612d910152612edf0152613d866000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c8063074a6de914610046578063414f826d14610072578063cbc1343414610087575b600080fd5b61005961005436600461378d565b61009a565b6040805192835260208301919091520160405180910390f35b6100856100803660046137e4565b6100b5565b005b61005961009536600461378d565b6100c3565b6000806100a88585856100d1565b915091505b935093915050565b6100bf82826101a3565b5050565b6000806100a8858585610286565b6000806100dc61041c565b6100e58361045f565b60006100ef6104be565b90506101066100fc6104d6565b8260046001610502565b506101143387838888610cd1565b90935091506101266020850185613828565b73ffffffffffffffffffffffffffffffffffffffff16337f07210cf9a89fae8012341fdc131255728787856379269f07c2e41c23b3c09b5884868561017160408b0160208c01613853565b61017e60408c018c613870565b6040516101909695949392919061391e565b60405180910390a3506100ad6001600055565b6101ab61041c565b60006101b56104be565b6000848152600860205260409020600101549091506fffffffffffffffffffffffffffffffff16156101f2576101eb8183610eba565b505061027c565b60006101fc6104d6565b90506102287f000000000000000000000000000000000000000000000000000000000000000085613987565b15158061023457508381105b1561026b576040517fecd29e8100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6102788483856000610502565b5050505b6100bf6001600055565b60008061029161041c565b61029a8361045f565b7f00000000000000000000000000000000000000000000000000000000000000008510156102f4576040517f423bbb4600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006102fe6104be565b905061030b6100fc6104d6565b5061031860003388611209565b61034f7f03000000000000000000000000000000000000000000000000000000000000006103496020870187613828565b88611323565b600061036a6103616020870187613828565b88848989610cd1565b909450905061037981886139ca565b92506000610386836113dd565b5090506103966020870187613828565b73ffffffffffffffffffffffffffffffffffffffff16337f1c7999deb68182de77ce89d32f82d0e13eb042921b2bfa9f35aa1c43f62f261e8a88876103e160408d0160208e01613853565b8a888e80604001906103f39190613870565b6040516104079897969594939291906139dd565b60405180910390a35050506100ad6001600055565b600260005403610458576040517f3ee5aeb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002600055565b600061046e6020830183613828565b73ffffffffffffffffffffffffffffffffffffffff16036104bb576040517ff0dd15fd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50565b60006104d1670de0b6b3a7640000611532565b905090565b60006104d1427f00000000000000000000000000000000000000000000000000000000000000006116c5565b600084815260086020526040812060018101546fffffffffffffffffffffffffffffffff1615158061053357504286115b1561055557600101546fffffffffffffffffffffffffffffffff169050610cc9565b60008060006105626104d6565b905060006105907f00000000000000000000000000000000000000000000000000000000000000008b613a25565b90505b81811015610618576000818152600860205260409020600101546fffffffffffffffffffffffffffffffff1680156105f0576000828152600860205260409020549094506fffffffffffffffffffffffffffffffff169250610618565b507f000000000000000000000000000000000000000000000000000000000000000001610593565b836000036106a15788935061069e61062e6116e2565b60025470010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff167f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000061170c565b92505b506106ab8361172c565b6001850180547fffffffffffffffffffffffffffffffff00000000000000000000000000000000166fffffffffffffffffffffffffffffffff9290921691909117905561072261071b7f00000000000000000000000000000000000000000000000000000000000000008b6139ca565b8a8461177c565b6107608961075a6107537f000000000000000000000000000000000000000000000000000000000000000083613a25565b4290611931565b8461177c565b61076988611946565b506000905060088161079b7f00000000000000000000000000000000000000000000000000000000000000008d6139ca565b815260208101919091526040016000908120600101546fffffffffffffffffffffffffffffffff1691506107d060028c611b5c565b6000818152600b60205260408120549192508c8c831561095357600192506000806107ff86898d866000611bc2565b9150915080600960008282546108159190613a25565b9091555061083190508660008461082b81611c91565b88611ced565b61083b8183613a25565b915061086b86838a8e877f0000000000000000000000000000000000000000000000000000000000000000611f14565b915061087f61087a8385611f5e565b611f73565b600680546002906108a79084906201000090046dffffffffffffffffffffffffffff16613a38565b92506101000a8154816dffffffffffffffffffffffffffff02191690836dffffffffffffffffffffffffffff1602179055506108e28261172c565b6006805460109061091a90849070010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff16613a5f565b92506101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff16021790555050505b6000610960600184611b5c565b6000818152600b60205260409020549091508015610aac576001945060008061098d838b8f886001611bc2565b9150915080600960008282546109a39190613a25565b909155506109bf9050836000846109b981611c91565b8a611fbd565b6109c981836139ca565b91506109d861087a8387611f5e565b60068054600290610a009084906201000090046dffffffffffffffffffffffffffff16613a38565b92506101000a8154816dffffffffffffffffffffffffffff02191690836dffffffffffffffffffffffffffff160217905550610a3b8261172c565b60068054601090610a7390849070010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff16613a5f565b92506101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff16021790555050505b8415610ae657610ad8610abe87611c91565b610ac783611c91565b610ad19190613a88565b6000612277565b8e610ae38482610eba565b50505b6000610af1846113dd565b50604080518e815260208101879052908101899052606081018490526080810182905290915085907fff888cf98d2696e95c8c39aa98c9ad55a5378008f7a56614c9353b7137a57ab79060a00160405180910390a260008f905060007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663f25964586040518163ffffffff1660e01b8152600401602060405180830381865afa158015610bb8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bdc9190613aa8565b905073ffffffffffffffffffffffffffffffffffffffff811615610cb757610cb45a604051336024820152604481018a905284151560648201526000908190608401604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f69114d4e0000000000000000000000000000000000000000000000000000000017905273ffffffffffffffffffffffffffffffffffffffff8616939291906122e0565b50505b509b9d50505050505050505050505050505b949350505050565b600080610cdd8561236b565b505060075485906fffffffffffffffffffffffffffffffff1680821115610d1457806fffffffffffffffffffffffffffffffff1691505b81600003610d29576000809250925050610eb0565b610d547f03000000000000000000000000000000000000000000000000000000000000008984611209565b600754600090610d909084906fffffffffffffffffffffffffffffffff7001000000000000000000000000000000009091048116908516612378565b9050610d9b8361172c565b610da59083613ac5565b600780547fffffffffffffffffffffffffffffffff00000000000000000000000000000000166fffffffffffffffffffffffffffffffff92909216919091179055610def8161172c565b60078054601090610e2790849070010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff16613ac5565b92506101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff160217905550610e688188876123b4565b9350610e74868461244d565b841015610ead576040517fc972651700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50505b9550959350505050565b6007547f03000000000000000000000000000000000000000000000000000000000000006000908152600b6020527f3ae204c42bf80d9df0ca83c69a5573417a7a5570428fcb513b3a0276db3e75435490918291610f2a916fffffffffffffffffffffffffffffffff16906139ca565b905080600003610f3e576001915050611203565b6000610f4985612462565b905080600003610f5e57600192505050611203565b600080610f6c838589612519565b9150915080610f82576000945050505050611203565b604080517f2c03ef6800000000000000000000000000000000000000000000000000000000815283518051600483015260208082015160248401528184015160448401526060808301516064850152608080840151608486015260a08085015160a487015260c08086015160c488015260e08681015160e4890152610100808801516101048a01526101208801516101248a01526101408801516101448a015261016090970151610164890152948a0151610184880152968901516101a4870152918801516101c48601528701516101e48501528601516102048401529285015161022483015291840151610244820152908301516102648201526102848101879052600090819073df5d682404b0611f46f2626d9d5a37eb6a6fd27d90632c03ef68906102a4016040805180830381865af41580156110c6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110ea9190613aee565b915091506111086110fa82611c91565b61110390613b12565b61268d565b92508261111e5760009650505050505050611203565b6111278261172c565b6007805460009061114b9084906fffffffffffffffffffffffffffffffff16613a5f565b92506101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff16021790555061118a8161172c565b600780546010906111c290849070010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff16613a5f565b92506101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff160217905550600196505050505050505b92915050565b6000838152600a6020908152604080832073ffffffffffffffffffffffffffffffffffffffff86168452909152902054811115611272576040517ff4d678b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000838152600a6020908152604080832073ffffffffffffffffffffffffffffffffffffffff86168452825280832080548590039055858352600b909152812080548392906112c29084906139ca565b9091555050604080518481526020810183905260009173ffffffffffffffffffffffffffffffffffffffff85169133917fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6291015b60405180910390a4505050565b6000838152600a6020908152604080832073ffffffffffffffffffffffffffffffffffffffff8616845290915281208054839290611362908490613a25565b90915550506000838152600b602052604081208054839290611385908490613a25565b9091555050604080518481526020810183905273ffffffffffffffffffffffffffffffffffffffff84169160009133917fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f629101611316565b60008060008073df5d682404b0611f46f2626d9d5a37eb6a6fd27d635a1b419e611406876128ac565b6040518263ffffffff1660e01b81526004016114229190613b4a565b6040805180830381865af415801561143e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114629190613bca565b9150915080611478575060009485945092505050565b600754600b6020527f3ae204c42bf80d9df0ca83c69a5573417a7a5570428fcb513b3a0276db3e75435460008080527fdf7de25b7f1fd6d0b5205f0e18f1f35bd7b8d84cce336588d184533ce43a6f765490926fffffffffffffffffffffffffffffffff16916114e791613a25565b6114f191906139ca565b90508060000361150957506000958695509350505050565b6000808711611519576000611524565b611524848884612378565b976001975095505050505050565b6040517f3c2063f000000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000811660048301527f0000000000000000000000000000000000000000000000000000000000000000811660248301527f0000000000000000000000000000000000000000000000000000000000000000811660448301527f0000000000000000000000000000000000000000000000000000000000000000811660648301527f00000000000000000000000000000000000000000000000000000000000000001660848201527f000000000000000000000000000000000000000000000000000000000000000060a482015260c48101829052600090731a4cee4e32ea51ec7671a0fd7333ca64fbf004f090633c2063f09060e4015b602060405180830381865af41580156116a1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112039190613bfa565b60006116d18284613987565b6116db90846139ca565b9392505050565b6002546004546000916104d1916fffffffffffffffffffffffffffffffff90911690600f0b612a6e565b60006117238261171d858888612378565b90612a94565b95945050505050565b60006fffffffffffffffffffffffffffffffff821115611778576040517f1e15f2a200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5090565b60008381526008602052604090205470010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff168083036117be5750505050565b6000848152600860205260408120546fffffffffffffffffffffffffffffffff1690819003611842576117f08361172c565b600086815260086020526040902080547fffffffffffffffffffffffffffffffff00000000000000000000000000000000166fffffffffffffffffffffffffffffffff929092169190911790556118e1565b61189361188e670de0b6b3a764000061185b88866139ca565b6118659190613c13565b85670de0b6b3a7640000611879878a6139ca565b6118839190613c13565b859291906001612b09565b61172c565b600086815260086020526040902080547fffffffffffffffffffffffffffffffff00000000000000000000000000000000166fffffffffffffffffffffffffffffffff929092169190911790555b6118ea8461172c565b60009586526008602052604090952080546fffffffffffffffffffffffffffffffff9687167001000000000000000000000000000000000296169590951790945550505050565b600081831161194057826116db565b50919050565b600654600090819061197f90849070010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff16611f5e565b6006546201000090046dffffffffffffffffffffffffffff169250905081811115611b575760006119b083836139ca565b90506119bf61188e8286612ba7565b600680546010906119f790849070010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff16613ac5565b92506101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff1602179055506000611a428583612bbc90919063ffffffff16565b90506000611a70827f0000000000000000000000000000000000000000000000000000000000000000611f5e565b90508060096000828254611a849190613a25565b90915550611a94905081836139ca565b9150611a9f8261172c565b60028054600090611ac39084906fffffffffffffffffffffffffffffffff16613a5f565b92506101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff160217905550611b0282612bd1565b60048054600090611b17908490600f0b613c2a565b92506101000a8154816fffffffffffffffffffffffffffffffff0219169083600f0b6fffffffffffffffffffffffffffffffff1602179055508493505050505b915091565b60007effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff821115611bb7576040517fb7d0949700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5060f89190911b1790565b600080611bcf8785612bbc565b91506000611bfd837f0000000000000000000000000000000000000000000000000000000000000000611f5e565b9050611c29817f0000000000000000000000000000000000000000000000000000000000000000611f5e565b91508315611c4c57611c3b82826139ca565b611c4590846139ca565b9250611c63565b611c5682826139ca565b611c609084613a25565b92505b86861015611c8657611c76838789612378565b9250611c83828789612378565b91505b509550959350505050565b60007f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff821115611778576040517f72dd4e0200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60045470010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff16611d6261188e82611d2f670de0b6b3a764000086613c13565b60055470010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff1691908a6000612b09565b600580546fffffffffffffffffffffffffffffffff928316700100000000000000000000000000000000029216919091179055611d9e8661172c565b611da89082613ac5565b600480546fffffffffffffffffffffffffffffffff928316700100000000000000000000000000000000029216919091179055611de48461172c565b60028054600090611e089084906fffffffffffffffffffffffffffffffff16613a5f565b92506101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff160217905550611e4783612c1d565b60048054600090611e5c908490600f0b613c2a565b92506101000a8154816fffffffffffffffffffffffffffffffff0219169083600f0b6fffffffffffffffffffffffffffffffff160217905550611e9e8561172c565b60028054601090611ed690849070010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff16613ac5565b92506101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff160217905550505050505050565b600080611f2c84611f268a888a612378565b90612bbc565b9050611f39888486612378565b611f439082613a25565b905086811115611f535786810391505b509695505050505050565b60006116db8383670de0b6b3a7640000612378565b60006dffffffffffffffffffffffffffff821115611778576040517f10d62a2e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002546fffffffffffffffffffffffffffffffff168381108061200857507f000000000000000000000000000000000000000000000000000000000000000061200685836139ca565b105b1561201557612015612c93565b6004549084900390600f0b61202a8482613a88565b90508361203686611c91565b13801561206b57507f00000000000000000000000000000000000000000000000000000000000000006120698383612a6e565b105b1561207857612078612c93565b60035470010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff166120d961188e826120ba670de0b6b3a764000088613c13565b6005546fffffffffffffffffffffffffffffffff1691908c6000612b09565b600580547fffffffffffffffffffffffffffffffff00000000000000000000000000000000166fffffffffffffffffffffffffffffffff9290921691909117905561212488826139ca565b905061212f8161172c565b600380546fffffffffffffffffffffffffffffffff92831670010000000000000000000000000000000002921691909117905561216b8361172c565b600280547fffffffffffffffffffffffffffffffff00000000000000000000000000000000166fffffffffffffffffffffffffffffffff929092169190911790556121b582612c1d565b600480547fffffffffffffffffffffffffffffffff00000000000000000000000000000000166fffffffffffffffffffffffffffffffff929092169190911790556121ff8761172c565b6002805460109061223790849070010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff16613a5f565b92506101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff1602179055505050505050505050565b60035461229b9061188e906fffffffffffffffffffffffffffffffff168484612cc5565b600380547fffffffffffffffffffffffffffffffff00000000000000000000000000000000166fffffffffffffffffffffffffffffffff929092169190911790555050565b6000606060008060008661ffff1667ffffffffffffffff81111561230657612306613c78565b6040519080825280601f01601f191660200182016040528015612330576020820181803683370190505b5090506000808751602089018b8e8ef191503d925086831115612351578692505b828152826000602083013e90999098509650505050505050565b6000611203826004610eba565b6000827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04841183021582026123ad57600080fd5b5091020490565b6000806123c18585611f5e565b90506123cc81612d2c565b9450846000036123e05760009150506116db565b8491506123f36040840160208501613853565b156124225761241b856124096020860186613828565b6124166040870187613870565b612e82565b9150612445565b612445856124336020860186613828565b6124406040870187613870565b6130e4565b509392505050565b60006116db8383670de0b6b3a7640000613116565b6003546000908190612486906fffffffffffffffffffffffffffffffff1684612ba7565b90506124b27f000000000000000000000000000000000000000000000000000000000000000082613a25565b6002546fffffffffffffffffffffffffffffffff161115611940576002547f00000000000000000000000000000000000000000000000000000000000000009061250f9083906fffffffffffffffffffffffffffffffff166139ca565b6116db91906139ca565b6125216136df565b60008061252d846128ac565b9050600073df5d682404b0611f46f2626d9d5a37eb6a6fd27d635a1b419e836040518263ffffffff1660e01b81526004016125689190613b4a565b6040805180830381865af4158015612584573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906125a89190613bca565b93509050826125bd5750600091506100ad9050565b60006125e46125df846101600151856101400151611f5e90919063ffffffff16565b611c91565b6126046125df85610120015186610100015161244d90919063ffffffff16565b61260e9190613a88565b604080516101208101825285815260208082019590955260008052600b85527fdf7de25b7f1fd6d0b5205f0e18f1f35bd7b8d84cce336588d184533ce43a6f7654818301526060810199909952608089019990995260a088015250815160c087015281015160e086015290940151610100840152509092600192509050565b600254600480546040517fd0b457ce0000000000000000000000000000000000000000000000000000000081526fffffffffffffffffffffffffffffffff808516938201849052600f9290920b60248201819052700100000000000000000000000000000000909404909116604482018190527f000000000000000000000000000000000000000000000000000000000000000060648301526084820185905260009391849081908190819073df5d682404b0611f46f2626d9d5a37eb6a6fd27d9063d0b457ce9060a401608060405180830381865af4158015612775573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906127999190613ca7565b9350935093509350806127b55750600098975050505050505050565b868414612807576127c58461172c565b600280547fffffffffffffffffffffffffffffffff00000000000000000000000000000000166fffffffffffffffffffffffffffffffff929092169190911790555b8583146128595761281783612c1d565b600480547fffffffffffffffffffffffffffffffff00000000000000000000000000000000166fffffffffffffffffffffffffffffffff929092169190911790555b84821461289d576128698261172c565b600280546fffffffffffffffffffffffffffffffff9283167001000000000000000000000000000000000292169190911790555b50600198975050505050505050565b6129106040518061018001604052806000815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b60408051610180810182526002546fffffffffffffffffffffffffffffffff8082168352600454600f0b602084015270010000000000000000000000000000000091829004811693830193909352606082018590527f000000000000000000000000000000000000000000000000000000000000000060808301527f000000000000000000000000000000000000000000000000000000000000000060a08301527f000000000000000000000000000000000000000000000000000000000000000060c08301527f000000000000000000000000000000000000000000000000000000000000000060e08301526003540482166101008201526005549091610120830191612a1e911661315a565b81526004546fffffffffffffffffffffffffffffffff7001000000000000000000000000000000009182900481166020840152600554604090930192612a669290041661315a565b905292915050565b600080612a7b84846131d0565b909250905080612a8d57612a8d612c93565b5092915050565b600081600003612aad5750670de0b6b3a7640000611203565b82600003612abd57506000611203565b6000612ac883611c91565b90506000612add612ad886611c91565b61320f565b9050818102612af4670de0b6b3a764000082613ce8565b9050612aff816134b0565b9695505050505050565b600082600003612b1a575084611723565b8115612b6d57612b4b612b2d8487613a25565b612b378587611f5e565b612b41888a611f5e565b611f269190613a25565b90506000612b598588611931565b905080821015612b67578091505b50611723565b828503612b7c57506000611723565b612aff612b8984876139ca565b612b93858761244d565b612b9d888a611f5e565b611f2691906139ca565b60006116db83670de0b6b3a764000084613116565b60006116db83670de0b6b3a764000084612378565b60006f7fffffffffffffffffffffffffffffff821115611778576040517fa5353be500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60007fffffffffffffffffffffffffffffffff80000000000000000000000000000000821280612c5c57506f7fffffffffffffffffffffffffffffff82135b15611778576040517fa5353be500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517fbb55fd2700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600080612cd38460006136c9565b612cde8460006136c9565b612ce89190613a88565b90506000811315612d0457612cfd8186613a25565b9450612d23565b6000811215612d2357612d1681613b12565b612d2090866139ca565b94505b50929392505050565b6040517f49124ca300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000811660048301527f0000000000000000000000000000000000000000000000000000000000000000811660248301527f0000000000000000000000000000000000000000000000000000000000000000811660448301527f0000000000000000000000000000000000000000000000000000000000000000811660648301527f00000000000000000000000000000000000000000000000000000000000000001660848201527f000000000000000000000000000000000000000000000000000000000000000060a482015260c48101829052600090731a4cee4e32ea51ec7671a0fd7333ca64fbf004f0906349124ca39060e401611684565b600080612e8e86611532565b905080156130db577f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16635c2bea496040518060a001604052807f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1681526020017f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1681526020017f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1681526020017f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1681526020017f0000000000000000000000000000000000000000000000000000000000000000815250612ffa89611532565b604080517fffffffff0000000000000000000000000000000000000000000000000000000060e086901b168152835173ffffffffffffffffffffffffffffffffffffffff90811660048301526020850151811660248301529184015182166044820152606084015182166064820152608090930151608484015260a4830191909152600060c48301523060e483015288166101048201526101240160408051808303816000875af11580156130b3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906130d79190613aee565b5091505b50949350505050565b6040517f6a17288200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048411830215820261314b57600080fd5b50910281810615159190040190565b600080670de0b6b3a764000061316e6104d6565b6131789190613c13565b9050808311613188576000613192565b61319281846139ca565b91506116db6131c9670de0b6b3a76400007f0000000000000000000000000000000000000000000000000000000000000000613c13565b8390612bbc565b6000806000836131df86611c91565b6131e99190613a88565b90506000811215613201576000809250925050613208565b9150600190505b9250929050565b600080821361324a576040517fe61b497500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b506fffffffffffffffffffffffffffffffff811160071b81811c67ffffffffffffffff1060061b1781811c63ffffffff1060051b1781811c61ffff1060041b1781811c60ff10600390811b90911782811c600f1060021b1782811c909110600190811b90911782811c90911017609f8181036060019290921b917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa08201906132f49084901c611c91565b6c465772b2bbbb5f824b15207a3081018102606090811d6d0388eaa27412d5aca026815d636e018202811d6d0df99ac502031bf953eff472fdcc018202811d6d13cdffb29d51d99322bdff5f2211018202811d6d0a0f742023def783a307a986912e018202811d6d01920d8043ca89b5239253284e42018202811d6c0b7a86d7375468fac667a0a527016c29508e458543d8aa4df2abee7883018302821d6d0139601a2efabe717e604cbb4894018302821d6d02247f7a7b6594320649aa03aba1018302821d7fffffffffffffffffffffffffffffffffffffff73c0c716a594e00d54e3c4cbc9018302821d7ffffffffffffffffffffffffffffffffffffffdc7b88c420e53a9890533129f6f01830290911d7fffffffffffffffffffffffffffffffffffffff465fda27eb4d63ded474e5f832019091027ffffffffffffffff5f6af8f7b3396644f18e157960000000000000000000000000105711340daa0d5f769dba1915cef59f0815a5506027d0267a36c0c95b3975ab3ee5b203a7614a3f75373f047d803ae7b6687f2b391909102017d57115e47018c7177eebf7cd370a3356a1b7863008a5ae8028c72b88642840160ae1d92915050565b60007ffffffffffffffffffffffffffffffffffffffffffffffffdb731c958f34d94c182136134e157506000919050565b680755bf798b4a1bf1e58212613523576040517f73a2d6b100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6503782dace9d9604e83901b059150600060606bb17217f7d1cf79abc9e3b39884821b056b80000000000000000000000001901d6bb17217f7d1cf79abc9e3b3988102909303927fffffffffffffffffffffffffffffffffffffffdbf3ccf1604d263450f02a550484018402606090811d6d0277594991cfc85f6e2461837cd9018502811d7fffffffffffffffffffffffffffffffffffffe5adedaa1cb095af9e4da10e363c018502811d6db1bbb201f443cf962f1a1d3db4a5018502811d7ffffffffffffffffffffffffffffffffffffd38dc772608b0ae56cce01296c0eb018502811d6e05180bb14799ab47a8a8cb2a527d57016d02d16720577bd19bf614176fe9ea6c10fe68e7fd37d0007b713f765087018702831d9081019087017ffffffffffffffffffffffffffffffffffffffe2c69812cf03b0763fd454a8f7e01810290921d6e0587f503bb6ea29d25fcb74019645001860279d835ebba824c98fb31b83b2ca45c000000000000000000000000018181059550929350909190612aff74029d9dc38563c32e5c2f6dc192ee70ef65f9978af3860260c38690031c611c91565b60008183136136d857816116db565b5090919050565b60405180610120016040528061374f6040518061018001604052806000815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b6000806000606084860312156137a257600080fd5b8335925060208401359150604084013567ffffffffffffffff8111156137c757600080fd5b8401606081870312156137d957600080fd5b809150509250925092565b600080604083850312156137f757600080fd5b50508035926020909101359150565b73ffffffffffffffffffffffffffffffffffffffff811681146104bb57600080fd5b60006020828403121561383a57600080fd5b81356116db81613806565b80151581146104bb57600080fd5b60006020828403121561386557600080fd5b81356116db81613845565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18436030181126138a557600080fd5b83018035915067ffffffffffffffff8211156138c057600080fd5b60200191503681900382131561320857600080fd5b8183528181602085013750600060208284010152600060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b868152856020820152846040820152831515606082015260a06080820152600061394c60a0830184866138d5565b98975050505050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b60008261399657613996613958565b500690565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b818103818111156112035761120361399b565b88815287602082015286604082015285151560608201528460808201528360a082015260e060c08201526000613a1760e0830184866138d5565b9a9950505050505050505050565b808201808211156112035761120361399b565b6dffffffffffffffffffffffffffff818116838216019080821115612a8d57612a8d61399b565b6fffffffffffffffffffffffffffffffff818116838216019080821115612a8d57612a8d61399b565b8181036000831280158383131683831282161715612a8d57612a8d61399b565b600060208284031215613aba57600080fd5b81516116db81613806565b6fffffffffffffffffffffffffffffffff828116828216039080821115612a8d57612a8d61399b565b60008060408385031215613b0157600080fd5b505080516020909101519092909150565b60007f80000000000000000000000000000000000000000000000000000000000000008203613b4357613b4361399b565b5060000390565b815181526020808301519082015260408083015190820152606080830151908201526080808301519082015260a0808301519082015260c0808301519082015260e0808301519082015261010080830151908201526101208083015190820152610140808301519082015261016080830151908201526101808101611203565b60008060408385031215613bdd57600080fd5b825191506020830151613bef81613845565b809150509250929050565b600060208284031215613c0c57600080fd5b5051919050565b80820281158282048414176112035761120361399b565b600f81810b9083900b016f7fffffffffffffffffffffffffffffff81137fffffffffffffffffffffffffffffffff80000000000000000000000000000000821217156112035761120361399b565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60008060008060808587031215613cbd57600080fd5b8451935060208501519250604085015191506060850151613cdd81613845565b939692955090935050565b600082613cf757613cf7613958565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff83147f800000000000000000000000000000000000000000000000000000000000000083141615613d4b57613d4b61399b565b50059056fea26469706673582212206af15b7cfc050c0a0be45100f558dec51a42edc02bab1fb9588e0920f7cb16a964736f6c63430008160033a26469706673582212200d975207cf4ac5e10a7d97e7613c96ed1ad126795d64eff9a33516407579742864736f6c63430008160033
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 30 Chains
Chain | Token | Portfolio % | Price | Amount | Value |
---|
Loading...
Loading
[ 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.