Contract Name:
RibbonThetaVault
Contract Source Code:
// SPDX-License-Identifier: MIT
pragma solidity =0.8.4;
import {SafeMath} from "@openzeppelin/contracts/utils/math/SafeMath.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {
SafeERC20
} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {GnosisAuction} from "../../libraries/GnosisAuction.sol";
import {
RibbonThetaVaultStorage
} from "../../storage/RibbonThetaVaultStorage.sol";
import {Vault} from "../../libraries/Vault.sol";
import {VaultLifecycle} from "../../libraries/VaultLifecycle.sol";
import {ShareMath} from "../../libraries/ShareMath.sol";
import {ILiquidityGauge} from "../../interfaces/ILiquidityGauge.sol";
import {IVaultPauser} from "../../interfaces/IVaultPauser.sol";
import {RibbonVault} from "./base/RibbonVault.sol";
/**
* UPGRADEABILITY: Since we use the upgradeable proxy pattern, we must observe
* the inheritance chain closely.
* Any changes/appends in storage variable needs to happen in RibbonThetaVaultStorage.
* RibbonThetaVault should not inherit from any other contract aside from RibbonVault, RibbonThetaVaultStorage
*/
contract RibbonThetaVault is RibbonVault, RibbonThetaVaultStorage {
using SafeERC20 for IERC20;
using SafeMath for uint256;
using ShareMath for Vault.DepositReceipt;
/************************************************
* IMMUTABLES & CONSTANTS
***********************************************/
/// @notice oTokenFactory is the factory contract used to spawn otokens. Used to lookup otokens.
address public immutable OTOKEN_FACTORY;
// The minimum duration for an option auction.
uint256 private constant MIN_AUCTION_DURATION = 5 minutes;
/************************************************
* EVENTS
***********************************************/
event OpenShort(
address indexed options,
uint256 depositAmount,
address indexed manager
);
event CloseShort(
address indexed options,
uint256 withdrawAmount,
address indexed manager
);
event NewOptionStrikeSelected(uint256 strikePrice, uint256 delta);
event PremiumDiscountSet(
uint256 premiumDiscount,
uint256 newPremiumDiscount
);
event AuctionDurationSet(
uint256 auctionDuration,
uint256 newAuctionDuration
);
event InstantWithdraw(
address indexed account,
uint256 amount,
uint256 round
);
event InitiateGnosisAuction(
address indexed auctioningToken,
address indexed biddingToken,
uint256 auctionCounter,
address indexed manager
);
/************************************************
* STRUCTS
***********************************************/
/**
* @notice Initialization parameters for the vault.
* @param _owner is the owner of the vault with critical permissions
* @param _feeRecipient is the address to recieve vault performance and management fees
* @param _managementFee is the management fee pct.
* @param _performanceFee is the perfomance fee pct.
* @param _tokenName is the name of the token
* @param _tokenSymbol is the symbol of the token
* @param _optionsPremiumPricer is the address of the contract with the
black-scholes premium calculation logic
* @param _strikeSelection is the address of the contract with strike selection logic
* @param _premiumDiscount is the vault's discount applied to the premium
* @param _auctionDuration is the duration of the gnosis auction
*/
struct InitParams {
address _owner;
address _keeper;
address _feeRecipient;
uint256 _managementFee;
uint256 _performanceFee;
string _tokenName;
string _tokenSymbol;
address _optionsPremiumPricer;
address _strikeSelection;
uint32 _premiumDiscount;
uint256 _auctionDuration;
}
/************************************************
* CONSTRUCTOR & INITIALIZATION
***********************************************/
/**
* @notice Initializes the contract with immutable variables
* @param _weth is the Wrapped Ether contract
* @param _usdc is the USDC contract
* @param _oTokenFactory is the contract address for minting new opyn option types (strikes, asset, expiry)
* @param _gammaController is the contract address for opyn actions
* @param _marginPool is the contract address for providing collateral to opyn
* @param _gnosisEasyAuction is the contract address that facilitates gnosis auctions
*/
constructor(
address _weth,
address _usdc,
address _oTokenFactory,
address _gammaController,
address _marginPool,
address _gnosisEasyAuction
)
RibbonVault(
_weth,
_usdc,
_gammaController,
_marginPool,
_gnosisEasyAuction
)
{
require(_oTokenFactory != address(0), "!_oTokenFactory");
OTOKEN_FACTORY = _oTokenFactory;
}
/**
* @notice Initializes the OptionVault contract with storage variables.
* @param _initParams is the struct with vault initialization parameters
* @param _vaultParams is the struct with vault general data
*/
function initialize(
InitParams calldata _initParams,
Vault.VaultParams calldata _vaultParams
) external initializer {
baseInitialize(
_initParams._owner,
_initParams._keeper,
_initParams._feeRecipient,
_initParams._managementFee,
_initParams._performanceFee,
_initParams._tokenName,
_initParams._tokenSymbol,
_vaultParams
);
require(
_initParams._optionsPremiumPricer != address(0),
"!_optionsPremiumPricer"
);
require(
_initParams._strikeSelection != address(0),
"!_strikeSelection"
);
require(
_initParams._premiumDiscount > 0 &&
_initParams._premiumDiscount <
100 * Vault.PREMIUM_DISCOUNT_MULTIPLIER,
"!_premiumDiscount"
);
require(
_initParams._auctionDuration >= MIN_AUCTION_DURATION,
"!_auctionDuration"
);
optionsPremiumPricer = _initParams._optionsPremiumPricer;
strikeSelection = _initParams._strikeSelection;
premiumDiscount = _initParams._premiumDiscount;
auctionDuration = _initParams._auctionDuration;
}
/************************************************
* SETTERS
***********************************************/
/**
* @notice Sets the new discount on premiums for options we are selling
* @param newPremiumDiscount is the premium discount
*/
function setPremiumDiscount(uint256 newPremiumDiscount)
external
onlyKeeper
{
require(
newPremiumDiscount > 0 &&
newPremiumDiscount <= 100 * Vault.PREMIUM_DISCOUNT_MULTIPLIER,
"Invalid discount"
);
emit PremiumDiscountSet(premiumDiscount, newPremiumDiscount);
premiumDiscount = newPremiumDiscount;
}
/**
* @notice Sets the new auction duration
* @param newAuctionDuration is the auction duration
*/
function setAuctionDuration(uint256 newAuctionDuration) external onlyOwner {
require(
newAuctionDuration >= MIN_AUCTION_DURATION,
"Invalid auction duration"
);
emit AuctionDurationSet(auctionDuration, newAuctionDuration);
auctionDuration = newAuctionDuration;
}
/**
* @notice Sets the new strike selection contract
* @param newStrikeSelection is the address of the new strike selection contract
*/
function setStrikeSelection(address newStrikeSelection) external onlyOwner {
require(newStrikeSelection != address(0), "!newStrikeSelection");
strikeSelection = newStrikeSelection;
}
/**
* @notice Sets the new options premium pricer contract
* @param newOptionsPremiumPricer is the address of the new strike selection contract
*/
function setOptionsPremiumPricer(address newOptionsPremiumPricer)
external
onlyOwner
{
require(
newOptionsPremiumPricer != address(0),
"!newOptionsPremiumPricer"
);
optionsPremiumPricer = newOptionsPremiumPricer;
}
/**
* @notice Optionality to set strike price manually
* @param strikePrice is the strike price of the new oTokens (decimals = 8)
*/
function setStrikePrice(uint128 strikePrice) external onlyOwner {
require(strikePrice > 0, "!strikePrice");
overriddenStrikePrice = strikePrice;
lastStrikeOverrideRound = vaultState.round;
}
/**
* @notice Sets the new liquidityGauge contract for this vault
* @param newLiquidityGauge is the address of the new liquidityGauge contract
*/
function setLiquidityGauge(address newLiquidityGauge) external onlyOwner {
liquidityGauge = newLiquidityGauge;
}
/**
* @notice Sets the new optionsPurchaseQueue contract for this vault
* @param newOptionsPurchaseQueue is the address of the new optionsPurchaseQueue contract
*/
function setOptionsPurchaseQueue(address newOptionsPurchaseQueue)
external
onlyOwner
{
optionsPurchaseQueue = newOptionsPurchaseQueue;
}
/**
* @notice Sets oToken Premium
* @param minPrice is the new oToken Premium in the units of 10**18
*/
function setMinPrice(uint256 minPrice) external onlyKeeper {
require(minPrice > 0, "!minPrice");
currentOtokenPremium = minPrice;
}
/**
* @notice Sets the new Vault Pauser contract for this vault
* @param newVaultPauser is the address of the new vaultPauser contract
*/
function setVaultPauser(address newVaultPauser) external onlyOwner {
vaultPauser = newVaultPauser;
}
/************************************************
* VAULT OPERATIONS
***********************************************/
/**
* @notice Withdraws the assets on the vault using the outstanding `DepositReceipt.amount`
* @param amount is the amount to withdraw
*/
function withdrawInstantly(uint256 amount) external nonReentrant {
Vault.DepositReceipt storage depositReceipt =
depositReceipts[msg.sender];
uint256 currentRound = vaultState.round;
require(amount > 0, "!amount");
require(depositReceipt.round == currentRound, "Invalid round");
uint256 receiptAmount = depositReceipt.amount;
require(receiptAmount >= amount, "Exceed amount");
// Subtraction underflow checks already ensure it is smaller than uint104
depositReceipt.amount = uint104(receiptAmount.sub(amount));
vaultState.totalPending = uint128(
uint256(vaultState.totalPending).sub(amount)
);
emit InstantWithdraw(msg.sender, amount, currentRound);
transferAsset(msg.sender, amount);
}
/**
* @notice Initiates a withdrawal that can be processed once the round completes
* @param numShares is the number of shares to withdraw
*/
function initiateWithdraw(uint256 numShares) external nonReentrant {
_initiateWithdraw(numShares);
currentQueuedWithdrawShares = currentQueuedWithdrawShares.add(
numShares
);
}
/**
* @notice Completes a scheduled withdrawal from a past round. Uses finalized pps for the round
*/
function completeWithdraw() external nonReentrant {
uint256 withdrawAmount = _completeWithdraw();
lastQueuedWithdrawAmount = uint128(
uint256(lastQueuedWithdrawAmount).sub(withdrawAmount)
);
}
/**
* @notice Stakes a users vault shares
* @param numShares is the number of shares to stake
*/
function stake(uint256 numShares) external nonReentrant {
address _liquidityGauge = liquidityGauge;
require(_liquidityGauge != address(0)); // Removed revert msgs due to contract size limit
require(numShares > 0);
uint256 heldByAccount = balanceOf(msg.sender);
if (heldByAccount < numShares) {
_redeem(numShares.sub(heldByAccount), false);
}
_transfer(msg.sender, address(this), numShares);
_approve(address(this), _liquidityGauge, numShares);
ILiquidityGauge(_liquidityGauge).deposit(numShares, msg.sender, false);
}
/**
* @notice Sets the next option the vault will be shorting, and closes the existing short.
* This allows all the users to withdraw if the next option is malicious.
*/
function commitAndClose() external nonReentrant {
address oldOption = optionState.currentOption;
VaultLifecycle.CloseParams memory closeParams =
VaultLifecycle.CloseParams({
OTOKEN_FACTORY: OTOKEN_FACTORY,
USDC: USDC,
currentOption: oldOption,
delay: DELAY,
lastStrikeOverrideRound: lastStrikeOverrideRound,
overriddenStrikePrice: overriddenStrikePrice,
strikeSelection: strikeSelection,
optionsPremiumPricer: optionsPremiumPricer,
premiumDiscount: premiumDiscount
});
(address otokenAddress, uint256 strikePrice, uint256 delta) =
VaultLifecycle.commitAndClose(closeParams, vaultParams, vaultState);
emit NewOptionStrikeSelected(strikePrice, delta);
optionState.nextOption = otokenAddress;
uint256 nextOptionReady = block.timestamp.add(DELAY);
require(
nextOptionReady <= type(uint32).max,
"Overflow nextOptionReady"
);
optionState.nextOptionReadyAt = uint32(nextOptionReady);
_closeShort(oldOption);
}
/**
* @notice Closes the existing short position for the vault.
*/
function _closeShort(address oldOption) private {
uint256 lockedAmount = vaultState.lockedAmount;
if (oldOption != address(0)) {
vaultState.lastLockedAmount = uint104(lockedAmount);
}
vaultState.lockedAmount = 0;
optionState.currentOption = address(0);
if (oldOption != address(0)) {
uint256 withdrawAmount =
VaultLifecycle.settleShort(GAMMA_CONTROLLER);
emit CloseShort(oldOption, withdrawAmount, msg.sender);
}
}
/**
* @notice Rolls the vault's funds into a new short position.
*/
function rollToNextOption() external onlyKeeper nonReentrant {
uint256 currQueuedWithdrawShares = currentQueuedWithdrawShares;
(
address newOption,
uint256 lockedBalance,
uint256 queuedWithdrawAmount
) =
_rollToNextOption(
lastQueuedWithdrawAmount,
currQueuedWithdrawShares
);
lastQueuedWithdrawAmount = queuedWithdrawAmount;
uint256 newQueuedWithdrawShares =
uint256(vaultState.queuedWithdrawShares).add(
currQueuedWithdrawShares
);
ShareMath.assertUint128(newQueuedWithdrawShares);
vaultState.queuedWithdrawShares = uint128(newQueuedWithdrawShares);
currentQueuedWithdrawShares = 0;
ShareMath.assertUint104(lockedBalance);
vaultState.lockedAmount = uint104(lockedBalance);
emit OpenShort(newOption, lockedBalance, msg.sender);
uint256 optionsMintAmount =
VaultLifecycle.createShort(
GAMMA_CONTROLLER,
MARGIN_POOL,
newOption,
lockedBalance
);
VaultLifecycle.allocateOptions(
optionsPurchaseQueue,
newOption,
optionsMintAmount,
VaultLifecycle.QUEUE_OPTION_ALLOCATION
);
_startAuction();
}
/**
* @notice Initiate the gnosis auction.
*/
function startAuction() external onlyKeeper nonReentrant {
_startAuction();
}
function _startAuction() private {
GnosisAuction.AuctionDetails memory auctionDetails;
address currentOtoken = optionState.currentOption;
auctionDetails.oTokenAddress = currentOtoken;
auctionDetails.gnosisEasyAuction = GNOSIS_EASY_AUCTION;
auctionDetails.asset = vaultParams.asset;
auctionDetails.assetDecimals = vaultParams.decimals;
auctionDetails.oTokenPremium = currentOtokenPremium;
auctionDetails.duration = auctionDuration;
optionAuctionID = VaultLifecycle.startAuction(auctionDetails);
}
/**
* @notice Sell the allocated options to the purchase queue post auction settlement
*/
function sellOptionsToQueue() external onlyKeeper nonReentrant {
VaultLifecycle.sellOptionsToQueue(
optionsPurchaseQueue,
GNOSIS_EASY_AUCTION,
optionAuctionID
);
}
/**
* @notice Burn the remaining oTokens left over from gnosis auction.
*/
function burnRemainingOTokens() external onlyKeeper nonReentrant {
uint256 unlockedAssetAmount =
VaultLifecycle.burnOtokens(
GAMMA_CONTROLLER,
optionState.currentOption
);
vaultState.lockedAmount = uint104(
uint256(vaultState.lockedAmount).sub(unlockedAssetAmount)
);
}
/**
* @notice Recovery function that returns an ERC20 token to the recipient
* @param token is the ERC20 token to recover from the vault
* @param recipient is the recipient of the recovered tokens
*/
function recoverTokens(address token, address recipient)
external
onlyOwner
{
require(token != vaultParams.asset, "Vault asset not recoverable");
require(token != address(this), "Vault share not recoverable");
require(recipient != address(this), "Recipient cannot be vault");
IERC20(token).safeTransfer(
recipient,
IERC20(token).balanceOf(address(this))
);
}
/**
* @notice pause a user's vault position
*/
function pausePosition() external {
address _vaultPauserAddress = vaultPauser;
require(_vaultPauserAddress != address(0)); // Removed revert msgs due to contract size limit
_redeem(0, true);
uint256 heldByAccount = balanceOf(msg.sender);
_approve(msg.sender, _vaultPauserAddress, heldByAccount);
IVaultPauser(_vaultPauserAddress).pausePosition(
msg.sender,
heldByAccount
);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// CAUTION
// This version of SafeMath should only be used with Solidity 0.8 or later,
// because it relies on the compiler's built in overflow checks.
/**
* @dev Wrappers over Solidity's arithmetic operations.
*
* NOTE: `SafeMath` is no longer needed starting with Solidity 0.8. The compiler
* now has built in overflow checking.
*/
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
uint256 c = a + b;
if (c < a) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the substraction of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b > a) return (false, 0);
return (true, a - b);
}
}
/**
* @dev Returns the multiplication of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) return (true, 0);
uint256 c = a * b;
if (c / a != b) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the division of two unsigned integers, with a division by zero flag.
*
* _Available since v3.4._
*/
function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a / b);
}
}
/**
* @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
*
* _Available since v3.4._
*/
function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a % b);
}
}
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
*
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
return a + b;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return a - b;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
*
* - Multiplication cannot overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
return a * b;
}
/**
* @dev Returns the integer division of two unsigned integers, reverting on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator.
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
return a / b;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* reverting when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
return a % b;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
* overflow (when the result is negative).
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {trySub}.
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(
uint256 a,
uint256 b,
string memory errorMessage
) internal pure returns (uint256) {
unchecked {
require(b <= a, errorMessage);
return a - b;
}
}
/**
* @dev Returns the integer division of two unsigned integers, reverting with custom message on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(
uint256 a,
uint256 b,
string memory errorMessage
) internal pure returns (uint256) {
unchecked {
require(b > 0, errorMessage);
return a / b;
}
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* reverting with custom message when dividing by zero.
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {tryMod}.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(
uint256 a,
uint256 b,
string memory errorMessage
) internal pure returns (uint256) {
unchecked {
require(b > 0, errorMessage);
return a % b;
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount) 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 `amount` 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 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
/**
* @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);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../../../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;
function safeTransfer(
IERC20 token,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(
IERC20 token,
address from,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(
IERC20 token,
address spender,
uint256 value
) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
function safeIncreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
uint256 newAllowance = token.allowance(address(this), spender) + value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
uint256 newAllowance = oldAllowance - value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
}
/**
* @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, "SafeERC20: low-level call failed");
if (returndata.length > 0) {
// Return data is optional
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity =0.8.4;
import {SafeMath} from "@openzeppelin/contracts/utils/math/SafeMath.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {
SafeERC20
} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {DSMath} from "../vendor/DSMath.sol";
import {IGnosisAuction} from "../interfaces/IGnosisAuction.sol";
import {IOtoken} from "../interfaces/GammaInterface.sol";
import {IOptionsPremiumPricer} from "../interfaces/IRibbon.sol";
import {Vault} from "./Vault.sol";
import {IRibbonThetaVault} from "../interfaces/IRibbonThetaVault.sol";
library GnosisAuction {
using SafeMath for uint256;
using SafeERC20 for IERC20;
event InitiateGnosisAuction(
address indexed auctioningToken,
address indexed biddingToken,
uint256 auctionCounter,
address indexed manager
);
event PlaceAuctionBid(
uint256 auctionId,
address indexed auctioningToken,
uint256 sellAmount,
uint256 buyAmount,
address indexed bidder
);
struct AuctionDetails {
address oTokenAddress;
address gnosisEasyAuction;
address asset;
uint256 assetDecimals;
uint256 oTokenPremium;
uint256 duration;
}
struct BidDetails {
address oTokenAddress;
address gnosisEasyAuction;
address asset;
uint256 assetDecimals;
uint256 auctionId;
uint256 lockedBalance;
uint256 optionAllocation;
uint256 optionPremium;
address bidder;
}
function startAuction(AuctionDetails calldata auctionDetails)
internal
returns (uint256 auctionID)
{
uint256 oTokenSellAmount =
getOTokenSellAmount(auctionDetails.oTokenAddress);
require(oTokenSellAmount > 0, "No otokens to sell");
IERC20(auctionDetails.oTokenAddress).safeApprove(
auctionDetails.gnosisEasyAuction,
IERC20(auctionDetails.oTokenAddress).balanceOf(address(this))
);
// minBidAmount is total oTokens to sell * premium per oToken
// shift decimals to correspond to decimals of USDC for puts
// and underlying for calls
uint256 minBidAmount =
DSMath.wmul(
oTokenSellAmount.mul(10**10),
auctionDetails.oTokenPremium
);
minBidAmount = auctionDetails.assetDecimals > 18
? minBidAmount.mul(10**(auctionDetails.assetDecimals.sub(18)))
: minBidAmount.div(
10**(uint256(18).sub(auctionDetails.assetDecimals))
);
require(
minBidAmount <= type(uint96).max,
"optionPremium * oTokenSellAmount > type(uint96) max value!"
);
uint256 auctionEnd = block.timestamp.add(auctionDetails.duration);
auctionID = IGnosisAuction(auctionDetails.gnosisEasyAuction)
.initiateAuction(
// address of oToken we minted and are selling
auctionDetails.oTokenAddress,
// address of asset we want in exchange for oTokens. Should match vault `asset`
auctionDetails.asset,
// orders can be cancelled at any time during the auction
auctionEnd,
// order will last for `duration`
auctionEnd,
// we are selling all of the otokens minus a fee taken by gnosis
uint96(oTokenSellAmount),
// the minimum we are willing to sell all the oTokens for. A discount is applied on black-scholes price
uint96(minBidAmount),
// the minimum bidding amount must be 1 * 10 ** -assetDecimals
1,
// the min funding threshold
0,
// no atomic closure
false,
// access manager contract
address(0),
// bytes for storing info like a whitelist for who can bid
bytes("")
);
emit InitiateGnosisAuction(
auctionDetails.oTokenAddress,
auctionDetails.asset,
auctionID,
msg.sender
);
}
function placeBid(BidDetails calldata bidDetails)
internal
returns (
uint256 sellAmount,
uint256 buyAmount,
uint64 userId
)
{
// calculate how much to allocate
sellAmount = bidDetails
.lockedBalance
.mul(bidDetails.optionAllocation)
.div(100 * Vault.OPTION_ALLOCATION_MULTIPLIER);
// divide the `asset` sellAmount by the target premium per oToken to
// get the number of oTokens to buy (8 decimals)
buyAmount = sellAmount
.mul(10**(bidDetails.assetDecimals.add(Vault.OTOKEN_DECIMALS)))
.div(bidDetails.optionPremium)
.div(10**bidDetails.assetDecimals);
require(
sellAmount <= type(uint96).max,
"sellAmount > type(uint96) max value!"
);
require(
buyAmount <= type(uint96).max,
"buyAmount > type(uint96) max value!"
);
// approve that amount
IERC20(bidDetails.asset).safeApprove(
bidDetails.gnosisEasyAuction,
sellAmount
);
uint96[] memory _minBuyAmounts = new uint96[](1);
uint96[] memory _sellAmounts = new uint96[](1);
bytes32[] memory _prevSellOrders = new bytes32[](1);
_minBuyAmounts[0] = uint96(buyAmount);
_sellAmounts[0] = uint96(sellAmount);
_prevSellOrders[
0
] = 0x0000000000000000000000000000000000000000000000000000000000000001;
// place sell order with that amount
userId = IGnosisAuction(bidDetails.gnosisEasyAuction).placeSellOrders(
bidDetails.auctionId,
_minBuyAmounts,
_sellAmounts,
_prevSellOrders,
"0x"
);
emit PlaceAuctionBid(
bidDetails.auctionId,
bidDetails.oTokenAddress,
sellAmount,
buyAmount,
bidDetails.bidder
);
return (sellAmount, buyAmount, userId);
}
function claimAuctionOtokens(
Vault.AuctionSellOrder calldata auctionSellOrder,
address gnosisEasyAuction,
address counterpartyThetaVault
) internal {
bytes32 order =
encodeOrder(
auctionSellOrder.userId,
auctionSellOrder.buyAmount,
auctionSellOrder.sellAmount
);
bytes32[] memory orders = new bytes32[](1);
orders[0] = order;
IGnosisAuction(gnosisEasyAuction).claimFromParticipantOrder(
IRibbonThetaVault(counterpartyThetaVault).optionAuctionID(),
orders
);
}
function getOTokenSellAmount(address oTokenAddress)
internal
view
returns (uint256)
{
// We take our current oToken balance. That will be our sell amount
// but otokens will be transferred to gnosis.
uint256 oTokenSellAmount =
IERC20(oTokenAddress).balanceOf(address(this));
require(
oTokenSellAmount <= type(uint96).max,
"oTokenSellAmount > type(uint96) max value!"
);
return oTokenSellAmount;
}
function getOTokenPremiumInStables(
address oTokenAddress,
address optionsPremiumPricer,
uint256 premiumDiscount
) internal view returns (uint256) {
IOtoken newOToken = IOtoken(oTokenAddress);
IOptionsPremiumPricer premiumPricer =
IOptionsPremiumPricer(optionsPremiumPricer);
// Apply black-scholes formula (from rvol library) to option given its features
// and get price for 100 contracts denominated USDC for both call and put options
uint256 optionPremium =
premiumPricer.getPremiumInStables(
newOToken.strikePrice(),
newOToken.expiryTimestamp(),
newOToken.isPut()
);
// Apply a discount to incentivize arbitraguers
optionPremium = optionPremium.mul(premiumDiscount).div(
100 * Vault.PREMIUM_DISCOUNT_MULTIPLIER
);
require(
optionPremium <= type(uint96).max,
"optionPremium > type(uint96) max value!"
);
return optionPremium;
}
function encodeOrder(
uint64 userId,
uint96 buyAmount,
uint96 sellAmount
) internal pure returns (bytes32) {
return
bytes32(
(uint256(userId) << 192) +
(uint256(buyAmount) << 96) +
uint256(sellAmount)
);
}
}
// SPDX-License-Identifier: MIT
pragma solidity =0.8.4;
abstract contract RibbonThetaVaultStorageV1 {
// Logic contract used to price options
address public optionsPremiumPricer;
// Logic contract used to select strike prices
address public strikeSelection;
// Premium discount on options we are selling (thousandths place: 000 - 999)
uint256 public premiumDiscount;
// Current oToken premium
uint256 public currentOtokenPremium;
// Last round id at which the strike was manually overridden
uint16 public lastStrikeOverrideRound;
// Price last overridden strike set to
uint256 public overriddenStrikePrice;
// Auction duration
uint256 public auctionDuration;
// Auction id of current option
uint256 public optionAuctionID;
}
abstract contract RibbonThetaVaultStorageV2 {
// Amount locked for scheduled withdrawals last week;
uint256 public lastQueuedWithdrawAmount;
}
abstract contract RibbonThetaVaultStorageV3 {
// DEPRECATED: Auction will be denominated in USDC if true
bool private _isUsdcAuction;
// DEPRECATED: Path for swaps
bytes private _swapPath;
}
abstract contract RibbonThetaVaultStorageV4 {
// LiquidityGauge contract for the vault
address public liquidityGauge;
}
abstract contract RibbonThetaVaultStorageV5 {
// OptionsPurchaseQueue contract for selling options
address public optionsPurchaseQueue;
}
abstract contract RibbonThetaVaultStorageV6 {
// Queued withdraw shares for the current round
uint256 public currentQueuedWithdrawShares;
}
abstract contract RibbonThetaVaultStorageV7 {
// Vault Pauser Contract for the vault
address public vaultPauser;
}
// We are following Compound's method of upgrading new contract implementations
// When we need to add new storage variables, we create a new version of RibbonThetaVaultStorage
// e.g. RibbonThetaVaultStorage<versionNumber>, so finally it would look like
// contract RibbonThetaVaultStorage is RibbonThetaVaultStorageV1, RibbonThetaVaultStorageV2
abstract contract RibbonThetaVaultStorage is
RibbonThetaVaultStorageV1,
RibbonThetaVaultStorageV2,
RibbonThetaVaultStorageV3,
RibbonThetaVaultStorageV4,
RibbonThetaVaultStorageV5,
RibbonThetaVaultStorageV6,
RibbonThetaVaultStorageV7
{
}
// SPDX-License-Identifier: MIT
pragma solidity =0.8.4;
library Vault {
/************************************************
* IMMUTABLES & CONSTANTS
***********************************************/
// Fees are 6-decimal places. For example: 20 * 10**6 = 20%
uint256 internal constant FEE_MULTIPLIER = 10**6;
// Premium discount has 1-decimal place. For example: 80 * 10**1 = 80%. Which represents a 20% discount.
uint256 internal constant PREMIUM_DISCOUNT_MULTIPLIER = 10;
// Otokens have 8 decimal places.
uint256 internal constant OTOKEN_DECIMALS = 8;
// Percentage of funds allocated to options is 2 decimal places. 10 * 10**2 = 10%
uint256 internal constant OPTION_ALLOCATION_MULTIPLIER = 10**2;
// Placeholder uint value to prevent cold writes
uint256 internal constant PLACEHOLDER_UINT = 1;
struct VaultParams {
// Option type the vault is selling
bool isPut;
// Token decimals for vault shares
uint8 decimals;
// Asset used in Theta / Delta Vault
address asset;
// Underlying asset of the options sold by vault
address underlying;
// Minimum supply of the vault shares issued, for ETH it's 10**10
uint56 minimumSupply;
// Vault cap
uint104 cap;
}
struct OptionState {
// Option that the vault is shorting / longing in the next cycle
address nextOption;
// Option that the vault is currently shorting / longing
address currentOption;
// The timestamp when the `nextOption` can be used by the vault
uint32 nextOptionReadyAt;
}
struct VaultState {
// 32 byte slot 1
// Current round number. `round` represents the number of `period`s elapsed.
uint16 round;
// Amount that is currently locked for selling options
uint104 lockedAmount;
// Amount that was locked for selling options in the previous round
// used for calculating performance fee deduction
uint104 lastLockedAmount;
// 32 byte slot 2
// Stores the total tally of how much of `asset` there is
// to be used to mint rTHETA tokens
uint128 totalPending;
// Total amount of queued withdrawal shares from previous rounds (doesn't include the current round)
uint128 queuedWithdrawShares;
}
struct DepositReceipt {
// Maximum of 65535 rounds. Assuming 1 round is 7 days, maximum is 1256 years.
uint16 round;
// Deposit amount, max 20,282,409,603,651 or 20 trillion ETH deposit
uint104 amount;
// Unredeemed shares balance
uint128 unredeemedShares;
}
struct Withdrawal {
// Maximum of 65535 rounds. Assuming 1 round is 7 days, maximum is 1256 years.
uint16 round;
// Number of shares withdrawn
uint128 shares;
}
struct AuctionSellOrder {
// Amount of `asset` token offered in auction
uint96 sellAmount;
// Amount of oToken requested in auction
uint96 buyAmount;
// User Id of delta vault in latest gnosis auction
uint64 userId;
}
}
// SPDX-License-Identifier: MIT
pragma solidity =0.8.4;
import {SafeMath} from "@openzeppelin/contracts/utils/math/SafeMath.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {Vault} from "./Vault.sol";
import {ShareMath} from "./ShareMath.sol";
import {IStrikeSelection} from "../interfaces/IRibbon.sol";
import {GnosisAuction} from "./GnosisAuction.sol";
import {
IOtokenFactory,
IOtoken,
IController,
GammaTypes
} from "../interfaces/GammaInterface.sol";
import {IERC20Detailed} from "../interfaces/IERC20Detailed.sol";
import {IGnosisAuction} from "../interfaces/IGnosisAuction.sol";
import {IOptionsPurchaseQueue} from "../interfaces/IOptionsPurchaseQueue.sol";
import {SupportsNonCompliantERC20} from "./SupportsNonCompliantERC20.sol";
import {IOptionsPremiumPricer} from "../interfaces/IRibbon.sol";
library VaultLifecycle {
using SafeMath for uint256;
using SupportsNonCompliantERC20 for IERC20;
struct CloseParams {
address OTOKEN_FACTORY;
address USDC;
address currentOption;
uint256 delay;
uint16 lastStrikeOverrideRound;
uint256 overriddenStrikePrice;
address strikeSelection;
address optionsPremiumPricer;
uint256 premiumDiscount;
}
/// @notice Default maximum option allocation for the queue (50%)
uint256 internal constant QUEUE_OPTION_ALLOCATION = 5000;
/**
* @notice Sets the next option the vault will be shorting, and calculates its premium for the auction
* @param closeParams is the struct with details on previous option and strike selection details
* @param vaultParams is the struct with vault general data
* @param vaultState is the struct with vault accounting state
* @return otokenAddress is the address of the new option
* @return strikePrice is the strike price of the new option
* @return delta is the delta of the new option
*/
function commitAndClose(
CloseParams calldata closeParams,
Vault.VaultParams storage vaultParams,
Vault.VaultState storage vaultState
)
external
returns (
address otokenAddress,
uint256 strikePrice,
uint256 delta
)
{
uint256 expiry = getNextExpiry(closeParams.currentOption);
IStrikeSelection selection =
IStrikeSelection(closeParams.strikeSelection);
bool isPut = vaultParams.isPut;
address underlying = vaultParams.underlying;
address asset = vaultParams.asset;
(strikePrice, delta) = closeParams.lastStrikeOverrideRound ==
vaultState.round
? (closeParams.overriddenStrikePrice, selection.delta())
: selection.getStrikePrice(expiry, isPut);
require(strikePrice != 0, "!strikePrice");
// retrieve address if option already exists, or deploy it
otokenAddress = getOrDeployOtoken(
closeParams,
vaultParams,
underlying,
asset,
strikePrice,
expiry,
isPut
);
return (otokenAddress, strikePrice, delta);
}
/**
* @notice Verify the otoken has the correct parameters to prevent vulnerability to opyn contract changes
* @param otokenAddress is the address of the otoken
* @param vaultParams is the struct with vault general data
* @param collateralAsset is the address of the collateral asset
* @param USDC is the address of usdc
* @param delay is the delay between commitAndClose and rollToNextOption
*/
function verifyOtoken(
address otokenAddress,
Vault.VaultParams storage vaultParams,
address collateralAsset,
address USDC,
uint256 delay
) private view {
require(otokenAddress != address(0), "!otokenAddress");
IOtoken otoken = IOtoken(otokenAddress);
require(otoken.isPut() == vaultParams.isPut, "Type mismatch");
require(
otoken.underlyingAsset() == vaultParams.underlying,
"Wrong underlyingAsset"
);
require(
otoken.collateralAsset() == collateralAsset,
"Wrong collateralAsset"
);
// we just assume all options use USDC as the strike
require(otoken.strikeAsset() == USDC, "strikeAsset != USDC");
uint256 readyAt = block.timestamp.add(delay);
require(otoken.expiryTimestamp() >= readyAt, "Expiry before delay");
}
/**
* @param decimals is the decimals of the asset
* @param totalBalance is the vaults total balance of the asset
* @param currentShareSupply is the supply of the shares invoked with totalSupply()
* @param lastQueuedWithdrawAmount is the total amount queued for withdrawals
* @param performanceFee is the perf fee percent to charge on premiums
* @param managementFee is the management fee percent to charge on the AUM
* @param currentQueuedWithdrawShares is amount of queued withdrawals from the current round
*/
struct RolloverParams {
uint256 decimals;
uint256 totalBalance;
uint256 currentShareSupply;
uint256 lastQueuedWithdrawAmount;
uint256 performanceFee;
uint256 managementFee;
uint256 currentQueuedWithdrawShares;
}
/**
* @notice Calculate the shares to mint, new price per share, and
amount of funds to re-allocate as collateral for the new round
* @param vaultState is the storage variable vaultState passed from RibbonVault
* @param params is the rollover parameters passed to compute the next state
* @return newLockedAmount is the amount of funds to allocate for the new round
* @return queuedWithdrawAmount is the amount of funds set aside for withdrawal
* @return newPricePerShare is the price per share of the new round
* @return mintShares is the amount of shares to mint from deposits
* @return performanceFeeInAsset is the performance fee charged by vault
* @return totalVaultFee is the total amount of fee charged by vault
*/
function rollover(
Vault.VaultState storage vaultState,
RolloverParams calldata params
)
external
view
returns (
uint256 newLockedAmount,
uint256 queuedWithdrawAmount,
uint256 newPricePerShare,
uint256 mintShares,
uint256 performanceFeeInAsset,
uint256 totalVaultFee
)
{
uint256 currentBalance = params.totalBalance;
uint256 pendingAmount = vaultState.totalPending;
// Total amount of queued withdrawal shares from previous rounds (doesn't include the current round)
uint256 lastQueuedWithdrawShares = vaultState.queuedWithdrawShares;
// Deduct older queued withdraws so we don't charge fees on them
uint256 balanceForVaultFees =
currentBalance.sub(params.lastQueuedWithdrawAmount);
{
(performanceFeeInAsset, , totalVaultFee) = VaultLifecycle
.getVaultFees(
balanceForVaultFees,
vaultState.lastLockedAmount,
vaultState.totalPending,
params.performanceFee,
params.managementFee
);
}
// Take into account the fee
// so we can calculate the newPricePerShare
currentBalance = currentBalance.sub(totalVaultFee);
{
newPricePerShare = ShareMath.pricePerShare(
params.currentShareSupply.sub(lastQueuedWithdrawShares),
currentBalance.sub(params.lastQueuedWithdrawAmount),
pendingAmount,
params.decimals
);
queuedWithdrawAmount = params.lastQueuedWithdrawAmount.add(
ShareMath.sharesToAsset(
params.currentQueuedWithdrawShares,
newPricePerShare,
params.decimals
)
);
// After closing the short, if the options expire in-the-money
// vault pricePerShare would go down because vault's asset balance decreased.
// This ensures that the newly-minted shares do not take on the loss.
mintShares = ShareMath.assetToShares(
pendingAmount,
newPricePerShare,
params.decimals
);
}
return (
currentBalance.sub(queuedWithdrawAmount), // new locked balance subtracts the queued withdrawals
queuedWithdrawAmount,
newPricePerShare,
mintShares,
performanceFeeInAsset,
totalVaultFee
);
}
/**
* @notice Creates the actual Opyn short position by depositing collateral and minting otokens
* @param gammaController is the address of the opyn controller contract
* @param marginPool is the address of the opyn margin contract which holds the collateral
* @param oTokenAddress is the address of the otoken to mint
* @param depositAmount is the amount of collateral to deposit
* @return the otoken mint amount
*/
function createShort(
address gammaController,
address marginPool,
address oTokenAddress,
uint256 depositAmount
) external returns (uint256) {
IController controller = IController(gammaController);
uint256 newVaultID =
(controller.getAccountVaultCounter(address(this))).add(1);
// An otoken's collateralAsset is the vault's `asset`
// So in the context of performing Opyn short operations we call them collateralAsset
IOtoken oToken = IOtoken(oTokenAddress);
address collateralAsset = oToken.collateralAsset();
uint256 collateralDecimals =
uint256(IERC20Detailed(collateralAsset).decimals());
uint256 mintAmount;
if (oToken.isPut()) {
// For minting puts, there will be instances where the full depositAmount will not be used for minting.
// This is because of an issue with precision.
//
// For ETH put options, we are calculating the mintAmount (10**8 decimals) using
// the depositAmount (10**18 decimals), which will result in truncation of decimals when scaling down.
// As a result, there will be tiny amounts of dust left behind in the Opyn vault when minting put otokens.
//
// For simplicity's sake, we do not refund the dust back to the address(this) on minting otokens.
// We retain the dust in the vault so the calling contract can withdraw the
// actual locked amount + dust at settlement.
//
// To test this behavior, we can console.log
// MarginCalculatorInterface(0x7A48d10f372b3D7c60f6c9770B91398e4ccfd3C7).getExcessCollateral(vault)
// to see how much dust (or excess collateral) is left behind.
mintAmount = depositAmount
.mul(10**Vault.OTOKEN_DECIMALS)
.mul(10**18) // we use 10**18 to give extra precision
.div(oToken.strikePrice().mul(10**(10 + collateralDecimals)));
} else {
mintAmount = depositAmount;
if (collateralDecimals > 8) {
uint256 scaleBy = 10**(collateralDecimals.sub(8)); // oTokens have 8 decimals
if (mintAmount > scaleBy) {
mintAmount = depositAmount.div(scaleBy); // scale down from 10**18 to 10**8
}
}
}
// double approve to fix non-compliant ERC20s
IERC20 collateralToken = IERC20(collateralAsset);
collateralToken.safeApproveNonCompliant(marginPool, depositAmount);
IController.ActionArgs[] memory actions =
new IController.ActionArgs[](3);
actions[0] = IController.ActionArgs(
IController.ActionType.OpenVault,
address(this), // owner
address(this), // receiver
address(0), // asset, otoken
newVaultID, // vaultId
0, // amount
0, //index
"" //data
);
actions[1] = IController.ActionArgs(
IController.ActionType.DepositCollateral,
address(this), // owner
address(this), // address to transfer from
collateralAsset, // deposited asset
newVaultID, // vaultId
depositAmount, // amount
0, //index
"" //data
);
actions[2] = IController.ActionArgs(
IController.ActionType.MintShortOption,
address(this), // owner
address(this), // address to transfer to
oTokenAddress, // option address
newVaultID, // vaultId
mintAmount, // amount
0, //index
"" //data
);
controller.operate(actions);
return mintAmount;
}
/**
* @notice Close the existing short otoken position. Currently this implementation is simple.
* It closes the most recent vault opened by the contract. This assumes that the contract will
* only have a single vault open at any given time. Since calling `_closeShort` deletes vaults by
calling SettleVault action, this assumption should hold.
* @param gammaController is the address of the opyn controller contract
* @return amount of collateral redeemed from the vault
*/
function settleShort(address gammaController) external returns (uint256) {
IController controller = IController(gammaController);
// gets the currently active vault ID
uint256 vaultID = controller.getAccountVaultCounter(address(this));
GammaTypes.Vault memory vault =
controller.getVault(address(this), vaultID);
require(vault.shortOtokens.length > 0, "No short");
// An otoken's collateralAsset is the vault's `asset`
// So in the context of performing Opyn short operations we call them collateralAsset
IERC20 collateralToken = IERC20(vault.collateralAssets[0]);
// The short position has been previously closed, or all the otokens have been burned.
// So we return early.
if (address(collateralToken) == address(0)) {
return 0;
}
// This is equivalent to doing IERC20(vault.asset).balanceOf(address(this))
uint256 startCollateralBalance =
collateralToken.balanceOf(address(this));
// If it is after expiry, we need to settle the short position using the normal way
// Delete the vault and withdraw all remaining collateral from the vault
IController.ActionArgs[] memory actions =
new IController.ActionArgs[](1);
actions[0] = IController.ActionArgs(
IController.ActionType.SettleVault,
address(this), // owner
address(this), // address to transfer to
address(0), // not used
vaultID, // vaultId
0, // not used
0, // not used
"" // not used
);
controller.operate(actions);
uint256 endCollateralBalance = collateralToken.balanceOf(address(this));
return endCollateralBalance.sub(startCollateralBalance);
}
/**
* @notice Exercises the ITM option using existing long otoken position. Currently this implementation is simple.
* It calls the `Redeem` action to claim the payout.
* @param gammaController is the address of the opyn controller contract
* @param oldOption is the address of the old option
* @param asset is the address of the vault's asset
* @return amount of asset received by exercising the option
*/
function settleLong(
address gammaController,
address oldOption,
address asset
) external returns (uint256) {
IController controller = IController(gammaController);
uint256 oldOptionBalance = IERC20(oldOption).balanceOf(address(this));
if (controller.getPayout(oldOption, oldOptionBalance) == 0) {
return 0;
}
uint256 startAssetBalance = IERC20(asset).balanceOf(address(this));
// If it is after expiry, we need to redeem the profits
IController.ActionArgs[] memory actions =
new IController.ActionArgs[](1);
actions[0] = IController.ActionArgs(
IController.ActionType.Redeem,
address(0), // not used
address(this), // address to send profits to
oldOption, // address of otoken
0, // not used
oldOptionBalance, // otoken balance
0, // not used
"" // not used
);
controller.operate(actions);
uint256 endAssetBalance = IERC20(asset).balanceOf(address(this));
return endAssetBalance.sub(startAssetBalance);
}
/**
* @notice Burn the remaining oTokens left over from auction. Currently this implementation is simple.
* It burns oTokens from the most recent vault opened by the contract. This assumes that the contract will
* only have a single vault open at any given time.
* @param gammaController is the address of the opyn controller contract
* @param currentOption is the address of the current option
* @return amount of collateral redeemed by burning otokens
*/
function burnOtokens(address gammaController, address currentOption)
external
returns (uint256)
{
uint256 numOTokensToBurn =
IERC20(currentOption).balanceOf(address(this));
require(numOTokensToBurn > 0, "No oTokens to burn");
IController controller = IController(gammaController);
// gets the currently active vault ID
uint256 vaultID = controller.getAccountVaultCounter(address(this));
GammaTypes.Vault memory vault =
controller.getVault(address(this), vaultID);
require(vault.shortOtokens.length > 0, "No short");
IERC20 collateralToken = IERC20(vault.collateralAssets[0]);
uint256 startCollateralBalance =
collateralToken.balanceOf(address(this));
// Burning `amount` of oTokens from the ribbon vault,
// then withdrawing the corresponding collateral amount from the vault
IController.ActionArgs[] memory actions =
new IController.ActionArgs[](2);
actions[0] = IController.ActionArgs(
IController.ActionType.BurnShortOption,
address(this), // owner
address(this), // address to transfer from
address(vault.shortOtokens[0]), // otoken address
vaultID, // vaultId
numOTokensToBurn, // amount
0, //index
"" //data
);
actions[1] = IController.ActionArgs(
IController.ActionType.WithdrawCollateral,
address(this), // owner
address(this), // address to transfer to
address(collateralToken), // withdrawn asset
vaultID, // vaultId
vault.collateralAmounts[0].mul(numOTokensToBurn).div(
vault.shortAmounts[0]
), // amount
0, //index
"" //data
);
controller.operate(actions);
uint256 endCollateralBalance = collateralToken.balanceOf(address(this));
return endCollateralBalance.sub(startCollateralBalance);
}
/**
* @notice Calculates the performance and management fee for this week's round
* @param currentBalance is the balance of funds held on the vault after closing short
* @param lastLockedAmount is the amount of funds locked from the previous round
* @param pendingAmount is the pending deposit amount
* @param performanceFeePercent is the performance fee pct.
* @param managementFeePercent is the management fee pct.
* @return performanceFeeInAsset is the performance fee
* @return managementFeeInAsset is the management fee
* @return vaultFee is the total fees
*/
function getVaultFees(
uint256 currentBalance,
uint256 lastLockedAmount,
uint256 pendingAmount,
uint256 performanceFeePercent,
uint256 managementFeePercent
)
internal
pure
returns (
uint256 performanceFeeInAsset,
uint256 managementFeeInAsset,
uint256 vaultFee
)
{
// At the first round, currentBalance=0, pendingAmount>0
// so we just do not charge anything on the first round
uint256 lockedBalanceSansPending =
currentBalance > pendingAmount
? currentBalance.sub(pendingAmount)
: 0;
uint256 _performanceFeeInAsset;
uint256 _managementFeeInAsset;
uint256 _vaultFee;
// Take performance fee and management fee ONLY if difference between
// last week and this week's vault deposits, taking into account pending
// deposits and withdrawals, is positive. If it is negative, last week's
// option expired ITM past breakeven, and the vault took a loss so we
// do not collect performance fee for last week
if (lockedBalanceSansPending > lastLockedAmount) {
_performanceFeeInAsset = performanceFeePercent > 0
? lockedBalanceSansPending
.sub(lastLockedAmount)
.mul(performanceFeePercent)
.div(100 * Vault.FEE_MULTIPLIER)
: 0;
_managementFeeInAsset = managementFeePercent > 0
? lockedBalanceSansPending.mul(managementFeePercent).div(
100 * Vault.FEE_MULTIPLIER
)
: 0;
_vaultFee = _performanceFeeInAsset.add(_managementFeeInAsset);
}
return (_performanceFeeInAsset, _managementFeeInAsset, _vaultFee);
}
/**
* @notice Either retrieves the option token if it already exists, or deploy it
* @param closeParams is the struct with details on previous option and strike selection details
* @param vaultParams is the struct with vault general data
* @param underlying is the address of the underlying asset of the option
* @param collateralAsset is the address of the collateral asset of the option
* @param strikePrice is the strike price of the option
* @param expiry is the expiry timestamp of the option
* @param isPut is whether the option is a put
* @return the address of the option
*/
function getOrDeployOtoken(
CloseParams calldata closeParams,
Vault.VaultParams storage vaultParams,
address underlying,
address collateralAsset,
uint256 strikePrice,
uint256 expiry,
bool isPut
) internal returns (address) {
IOtokenFactory factory = IOtokenFactory(closeParams.OTOKEN_FACTORY);
address otokenFromFactory =
factory.getOtoken(
underlying,
closeParams.USDC,
collateralAsset,
strikePrice,
expiry,
isPut
);
if (otokenFromFactory != address(0)) {
return otokenFromFactory;
}
address otoken =
factory.createOtoken(
underlying,
closeParams.USDC,
collateralAsset,
strikePrice,
expiry,
isPut
);
verifyOtoken(
otoken,
vaultParams,
collateralAsset,
closeParams.USDC,
closeParams.delay
);
return otoken;
}
function getOTokenPremium(
address oTokenAddress,
address optionsPremiumPricer,
uint256 premiumDiscount
) external view returns (uint256) {
return
_getOTokenPremium(
oTokenAddress,
optionsPremiumPricer,
premiumDiscount
);
}
function _getOTokenPremium(
address oTokenAddress,
address optionsPremiumPricer,
uint256 premiumDiscount
) internal view returns (uint256) {
IOtoken newOToken = IOtoken(oTokenAddress);
IOptionsPremiumPricer premiumPricer =
IOptionsPremiumPricer(optionsPremiumPricer);
// Apply black-scholes formula (from rvol library) to option given its features
// and get price for 100 contracts denominated in the underlying asset for call option
// and USDC for put option
uint256 optionPremium =
premiumPricer.getPremium(
newOToken.strikePrice(),
newOToken.expiryTimestamp(),
newOToken.isPut()
);
// Apply a discount to incentivize arbitraguers
optionPremium = optionPremium.mul(premiumDiscount).div(
100 * Vault.PREMIUM_DISCOUNT_MULTIPLIER
);
require(
optionPremium <= type(uint96).max,
"optionPremium > type(uint96) max value!"
);
require(optionPremium > 0, "!optionPremium");
return optionPremium;
}
/**
* @notice Starts the gnosis auction
* @param auctionDetails is the struct with all the custom parameters of the auction
* @return the auction id of the newly created auction
*/
function startAuction(GnosisAuction.AuctionDetails calldata auctionDetails)
external
returns (uint256)
{
return GnosisAuction.startAuction(auctionDetails);
}
/**
* @notice Settles the gnosis auction
* @param gnosisEasyAuction is the contract address of Gnosis easy auction protocol
* @param auctionID is the auction ID of the gnosis easy auction
*/
function settleAuction(address gnosisEasyAuction, uint256 auctionID)
internal
{
IGnosisAuction(gnosisEasyAuction).settleAuction(auctionID);
}
/**
* @notice Places a bid in an auction
* @param bidDetails is the struct with all the details of the
bid including the auction's id and how much to bid
*/
function placeBid(GnosisAuction.BidDetails calldata bidDetails)
external
returns (
uint256 sellAmount,
uint256 buyAmount,
uint64 userId
)
{
return GnosisAuction.placeBid(bidDetails);
}
/**
* @notice Claims the oTokens belonging to the vault
* @param auctionSellOrder is the sell order of the bid
* @param gnosisEasyAuction is the address of the gnosis auction contract
holding custody to the funds
* @param counterpartyThetaVault is the address of the counterparty theta
vault of this delta vault
*/
function claimAuctionOtokens(
Vault.AuctionSellOrder calldata auctionSellOrder,
address gnosisEasyAuction,
address counterpartyThetaVault
) external {
GnosisAuction.claimAuctionOtokens(
auctionSellOrder,
gnosisEasyAuction,
counterpartyThetaVault
);
}
/**
* @notice Allocates the vault's minted options to the OptionsPurchaseQueue contract
* @dev Skipped if the optionsPurchaseQueue doesn't exist
* @param optionsPurchaseQueue is the OptionsPurchaseQueue contract
* @param option is the minted option
* @param optionsAmount is the amount of options minted
* @param optionAllocation is the maximum % of options to allocate towards the purchase queue (will only allocate
* up to the amount that is on the queue)
* @return allocatedOptions is the amount of options that ended up getting allocated to the OptionsPurchaseQueue
*/
function allocateOptions(
address optionsPurchaseQueue,
address option,
uint256 optionsAmount,
uint256 optionAllocation
) external returns (uint256 allocatedOptions) {
// Skip if optionsPurchaseQueue is address(0)
if (optionsPurchaseQueue != address(0)) {
allocatedOptions = optionsAmount.mul(optionAllocation).div(
100 * Vault.OPTION_ALLOCATION_MULTIPLIER
);
allocatedOptions = IOptionsPurchaseQueue(optionsPurchaseQueue)
.getOptionsAllocation(address(this), allocatedOptions);
if (allocatedOptions != 0) {
IERC20(option).approve(optionsPurchaseQueue, allocatedOptions);
IOptionsPurchaseQueue(optionsPurchaseQueue).allocateOptions(
allocatedOptions
);
}
}
return allocatedOptions;
}
/**
* @notice Sell the allocated options to the purchase queue post auction settlement
* @dev Reverts if the auction hasn't settled yet
* @param optionsPurchaseQueue is the OptionsPurchaseQueue contract
* @param gnosisEasyAuction The address of the Gnosis Easy Auction contract
* @return totalPremiums Total premiums earnt by the vault
*/
function sellOptionsToQueue(
address optionsPurchaseQueue,
address gnosisEasyAuction,
uint256 optionAuctionID
) external returns (uint256) {
uint256 settlementPrice =
getAuctionSettlementPrice(gnosisEasyAuction, optionAuctionID);
require(settlementPrice != 0, "!settlementPrice");
return
IOptionsPurchaseQueue(optionsPurchaseQueue).sellToBuyers(
settlementPrice
);
}
/**
* @notice Gets the settlement price of a settled auction
* @param gnosisEasyAuction The address of the Gnosis Easy Auction contract
* @return settlementPrice Auction settlement price
*/
function getAuctionSettlementPrice(
address gnosisEasyAuction,
uint256 optionAuctionID
) public view returns (uint256) {
bytes32 clearingPriceOrder =
IGnosisAuction(gnosisEasyAuction)
.auctionData(optionAuctionID)
.clearingPriceOrder;
if (clearingPriceOrder == bytes32(0)) {
// Current auction hasn't settled yet
return 0;
} else {
// We decode the clearingPriceOrder to find the auction settlement price
// settlementPrice = clearingPriceOrder.sellAmount / clearingPriceOrder.buyAmount
return
(10**Vault.OTOKEN_DECIMALS)
.mul(
uint96(uint256(clearingPriceOrder)) // sellAmount
)
.div(
uint96(uint256(clearingPriceOrder) >> 96) // buyAmount
);
}
}
/**
* @notice Verify the constructor params satisfy requirements
* @param owner is the owner of the vault with critical permissions
* @param feeRecipient is the address to recieve vault performance and management fees
* @param performanceFee is the perfomance fee pct.
* @param tokenName is the name of the token
* @param tokenSymbol is the symbol of the token
* @param _vaultParams is the struct with vault general data
*/
function verifyInitializerParams(
address owner,
address keeper,
address feeRecipient,
uint256 performanceFee,
uint256 managementFee,
string calldata tokenName,
string calldata tokenSymbol,
Vault.VaultParams calldata _vaultParams
) external pure {
require(owner != address(0), "!owner");
require(keeper != address(0), "!keeper");
require(feeRecipient != address(0), "!feeRecipient");
require(
performanceFee < 100 * Vault.FEE_MULTIPLIER,
"performanceFee >= 100%"
);
require(
managementFee < 100 * Vault.FEE_MULTIPLIER,
"managementFee >= 100%"
);
require(bytes(tokenName).length > 0, "!tokenName");
require(bytes(tokenSymbol).length > 0, "!tokenSymbol");
require(_vaultParams.asset != address(0), "!asset");
require(_vaultParams.underlying != address(0), "!underlying");
require(_vaultParams.minimumSupply > 0, "!minimumSupply");
require(_vaultParams.cap > 0, "!cap");
require(
_vaultParams.cap > _vaultParams.minimumSupply,
"cap has to be higher than minimumSupply"
);
}
/**
* @notice Gets the next option expiry timestamp
* @param currentOption is the otoken address that the vault is currently writing
*/
function getNextExpiry(address currentOption)
internal
view
returns (uint256)
{
// uninitialized state
if (currentOption == address(0)) {
return getNextFriday(block.timestamp);
}
uint256 currentExpiry = IOtoken(currentOption).expiryTimestamp();
// After options expiry if no options are written for >1 week
// We need to give the ability continue writing options
if (block.timestamp > currentExpiry + 7 days) {
return getNextFriday(block.timestamp);
}
return getNextFriday(currentExpiry);
}
/**
* @notice Gets the next options expiry timestamp
* @param timestamp is the expiry timestamp of the current option
* Reference: https://codereview.stackexchange.com/a/33532
* Examples:
* getNextFriday(week 1 thursday) -> week 1 friday
* getNextFriday(week 1 friday) -> week 2 friday
* getNextFriday(week 1 saturday) -> week 2 friday
*/
function getNextFriday(uint256 timestamp) internal pure returns (uint256) {
// dayOfWeek = 0 (sunday) - 6 (saturday)
uint256 dayOfWeek = ((timestamp / 1 days) + 4) % 7;
uint256 nextFriday = timestamp + ((7 + 5 - dayOfWeek) % 7) * 1 days;
uint256 friday8am = nextFriday - (nextFriday % (24 hours)) + (8 hours);
// If the passed timestamp is day=Friday hour>8am, we simply increment it by a week to next Friday
if (timestamp >= friday8am) {
friday8am += 7 days;
}
return friday8am;
}
}
// SPDX-License-Identifier: MIT
pragma solidity =0.8.4;
import {SafeMath} from "@openzeppelin/contracts/utils/math/SafeMath.sol";
import {Vault} from "./Vault.sol";
library ShareMath {
using SafeMath for uint256;
uint256 internal constant PLACEHOLDER_UINT = 1;
function assetToShares(
uint256 assetAmount,
uint256 assetPerShare,
uint256 decimals
) internal pure returns (uint256) {
// If this throws, it means that vault's roundPricePerShare[currentRound] has not been set yet
// which should never happen.
// Has to be larger than 1 because `1` is used in `initRoundPricePerShares` to prevent cold writes.
require(assetPerShare > PLACEHOLDER_UINT, "Invalid assetPerShare");
return assetAmount.mul(10**decimals).div(assetPerShare);
}
function sharesToAsset(
uint256 shares,
uint256 assetPerShare,
uint256 decimals
) internal pure returns (uint256) {
// If this throws, it means that vault's roundPricePerShare[currentRound] has not been set yet
// which should never happen.
// Has to be larger than 1 because `1` is used in `initRoundPricePerShares` to prevent cold writes.
require(assetPerShare > PLACEHOLDER_UINT, "Invalid assetPerShare");
return shares.mul(assetPerShare).div(10**decimals);
}
/**
* @notice Returns the shares unredeemed by the user given their DepositReceipt
* @param depositReceipt is the user's deposit receipt
* @param currentRound is the `round` stored on the vault
* @param assetPerShare is the price in asset per share
* @param decimals is the number of decimals the asset/shares use
* @return unredeemedShares is the user's virtual balance of shares that are owed
*/
function getSharesFromReceipt(
Vault.DepositReceipt memory depositReceipt,
uint256 currentRound,
uint256 assetPerShare,
uint256 decimals
) internal pure returns (uint256 unredeemedShares) {
if (depositReceipt.round > 0 && depositReceipt.round < currentRound) {
uint256 sharesFromRound =
assetToShares(depositReceipt.amount, assetPerShare, decimals);
return
uint256(depositReceipt.unredeemedShares).add(sharesFromRound);
}
return depositReceipt.unredeemedShares;
}
function pricePerShare(
uint256 totalSupply,
uint256 totalBalance,
uint256 pendingAmount,
uint256 decimals
) internal pure returns (uint256) {
uint256 singleShare = 10**decimals;
return
totalSupply > 0
? singleShare.mul(totalBalance.sub(pendingAmount)).div(
totalSupply
)
: singleShare;
}
/************************************************
* HELPERS
***********************************************/
function assertUint104(uint256 num) internal pure {
require(num <= type(uint104).max, "Overflow uint104");
}
function assertUint128(uint256 num) internal pure {
require(num <= type(uint128).max, "Overflow uint128");
}
}
// SPDX-License-Identifier: MIT
pragma solidity =0.8.4;
interface ILiquidityGauge {
function balanceOf(address) external view returns (uint256);
function deposit(
uint256 _value,
address _addr,
bool _claim_rewards
) external;
function withdraw(uint256 _value) external;
}
// SPDX-License-Identifier: MIT
pragma solidity =0.8.4;
interface IVaultPauser {
/// @notice pause vault position of an account with max amount
/// @param _account the address of user
/// @param _amount amount of shares
function pausePosition(address _account, uint256 _amount) external;
/// @notice resume vault position of an account with max amount
/// @param _vaultAddress the address of vault
function resumePosition(address _vaultAddress) external;
}
// SPDX-License-Identifier: MIT
pragma solidity =0.8.4;
import {SafeMath} from "@openzeppelin/contracts/utils/math/SafeMath.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {
SafeERC20
} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {
ReentrancyGuardUpgradeable
} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import {
OwnableUpgradeable
} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {
ERC20Upgradeable
} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import {Vault} from "../../../libraries/Vault.sol";
import {VaultLifecycle} from "../../../libraries/VaultLifecycle.sol";
import {ShareMath} from "../../../libraries/ShareMath.sol";
import {IWETH} from "../../../interfaces/IWETH.sol";
contract RibbonVault is
ReentrancyGuardUpgradeable,
OwnableUpgradeable,
ERC20Upgradeable
{
using SafeERC20 for IERC20;
using SafeMath for uint256;
using ShareMath for Vault.DepositReceipt;
/************************************************
* NON UPGRADEABLE STORAGE
***********************************************/
/// @notice Stores the user's pending deposit for the round
mapping(address => Vault.DepositReceipt) public depositReceipts;
/// @notice On every round's close, the pricePerShare value of an rTHETA token is stored
/// This is used to determine the number of shares to be returned
/// to a user with their DepositReceipt.depositAmount
mapping(uint256 => uint256) public roundPricePerShare;
/// @notice Stores pending user withdrawals
mapping(address => Vault.Withdrawal) public withdrawals;
/// @notice Vault's parameters like cap, decimals
Vault.VaultParams public vaultParams;
/// @notice Vault's lifecycle state like round and locked amounts
Vault.VaultState public vaultState;
/// @notice Vault's state of the options sold and the timelocked option
Vault.OptionState public optionState;
/// @notice Fee recipient for the performance and management fees
address public feeRecipient;
/// @notice role in charge of weekly vault operations such as rollToNextOption and burnRemainingOTokens
// no access to critical vault changes
address public keeper;
/// @notice Performance fee charged on premiums earned in rollToNextOption. Only charged when there is no loss.
uint256 public performanceFee;
/// @notice Management fee charged on entire AUM in rollToNextOption. Only charged when there is no loss.
uint256 public managementFee;
// Gap is left to avoid storage collisions. Though RibbonVault is not upgradeable, we add this as a safety measure.
uint256[30] private ____gap;
// *IMPORTANT* NO NEW STORAGE VARIABLES SHOULD BE ADDED HERE
// This is to prevent storage collisions. All storage variables should be appended to RibbonThetaVaultStorage
// or RibbonDeltaVaultStorage instead. Read this documentation to learn more:
// https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#modifying-your-contracts
/************************************************
* IMMUTABLES & CONSTANTS
***********************************************/
/// @notice WETH9 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
address public immutable WETH;
/// @notice USDC 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
address public immutable USDC;
/// @notice Deprecated: 15 minute timelock between commitAndClose and rollToNexOption.
uint256 public constant DELAY = 0;
/// @notice 7 day period between each options sale.
uint256 public constant PERIOD = 7 days;
// Number of weeks per year = 52.142857 weeks * FEE_MULTIPLIER = 52142857
// Dividing by weeks per year requires doing num.mul(FEE_MULTIPLIER).div(WEEKS_PER_YEAR)
uint256 private constant WEEKS_PER_YEAR = 52142857;
// GAMMA_CONTROLLER is the top-level contract in Gamma protocol
// which allows users to perform multiple actions on their vaults
// and positions https://github.com/opynfinance/GammaProtocol/blob/master/contracts/core/Controller.sol
address public immutable GAMMA_CONTROLLER;
// MARGIN_POOL is Gamma protocol's collateral pool.
// Needed to approve collateral.safeTransferFrom for minting otokens.
// https://github.com/opynfinance/GammaProtocol/blob/master/contracts/core/MarginPool.sol
address public immutable MARGIN_POOL;
// GNOSIS_EASY_AUCTION is Gnosis protocol's contract for initiating auctions and placing bids
// https://github.com/gnosis/ido-contracts/blob/main/contracts/EasyAuction.sol
address public immutable GNOSIS_EASY_AUCTION;
/************************************************
* EVENTS
***********************************************/
event Deposit(address indexed account, uint256 amount, uint256 round);
event InitiateWithdraw(
address indexed account,
uint256 shares,
uint256 round
);
event Redeem(address indexed account, uint256 share, uint256 round);
event ManagementFeeSet(uint256 managementFee, uint256 newManagementFee);
event PerformanceFeeSet(uint256 performanceFee, uint256 newPerformanceFee);
event CapSet(uint256 oldCap, uint256 newCap);
event Withdraw(address indexed account, uint256 amount, uint256 shares);
event CollectVaultFees(
uint256 performanceFee,
uint256 vaultFee,
uint256 round,
address indexed feeRecipient
);
/************************************************
* CONSTRUCTOR & INITIALIZATION
***********************************************/
/**
* @notice Initializes the contract with immutable variables
* @param _weth is the Wrapped Ether contract
* @param _usdc is the USDC contract
* @param _gammaController is the contract address for opyn actions
* @param _marginPool is the contract address for providing collateral to opyn
* @param _gnosisEasyAuction is the contract address that facilitates gnosis auctions
*/
constructor(
address _weth,
address _usdc,
address _gammaController,
address _marginPool,
address _gnosisEasyAuction
) {
require(_weth != address(0), "!_weth");
require(_usdc != address(0), "!_usdc");
require(_gnosisEasyAuction != address(0), "!_gnosisEasyAuction");
require(_gammaController != address(0), "!_gammaController");
require(_marginPool != address(0), "!_marginPool");
WETH = _weth;
USDC = _usdc;
GAMMA_CONTROLLER = _gammaController;
MARGIN_POOL = _marginPool;
GNOSIS_EASY_AUCTION = _gnosisEasyAuction;
}
/**
* @notice Initializes the OptionVault contract with storage variables.
*/
function baseInitialize(
address _owner,
address _keeper,
address _feeRecipient,
uint256 _managementFee,
uint256 _performanceFee,
string memory _tokenName,
string memory _tokenSymbol,
Vault.VaultParams calldata _vaultParams
) internal initializer {
VaultLifecycle.verifyInitializerParams(
_owner,
_keeper,
_feeRecipient,
_performanceFee,
_managementFee,
_tokenName,
_tokenSymbol,
_vaultParams
);
__ReentrancyGuard_init();
__ERC20_init(_tokenName, _tokenSymbol);
__Ownable_init();
transferOwnership(_owner);
keeper = _keeper;
feeRecipient = _feeRecipient;
performanceFee = _performanceFee;
managementFee = _managementFee.mul(Vault.FEE_MULTIPLIER).div(
WEEKS_PER_YEAR
);
vaultParams = _vaultParams;
uint256 assetBalance =
IERC20(vaultParams.asset).balanceOf(address(this));
ShareMath.assertUint104(assetBalance);
vaultState.lastLockedAmount = uint104(assetBalance);
vaultState.round = 1;
}
/**
* @dev Throws if called by any account other than the keeper.
*/
modifier onlyKeeper() {
require(msg.sender == keeper, "!keeper");
_;
}
/************************************************
* SETTERS
***********************************************/
/**
* @notice Sets the new keeper
* @param newKeeper is the address of the new keeper
*/
function setNewKeeper(address newKeeper) external onlyOwner {
require(newKeeper != address(0), "!newKeeper");
keeper = newKeeper;
}
/**
* @notice Sets the new fee recipient
* @param newFeeRecipient is the address of the new fee recipient
*/
function setFeeRecipient(address newFeeRecipient) external onlyOwner {
require(newFeeRecipient != address(0), "!newFeeRecipient");
require(newFeeRecipient != feeRecipient, "Must be new feeRecipient");
feeRecipient = newFeeRecipient;
}
/**
* @notice Sets the management fee for the vault
* @param newManagementFee is the management fee (6 decimals). ex: 2 * 10 ** 6 = 2%
*/
function setManagementFee(uint256 newManagementFee) external onlyOwner {
require(
newManagementFee < 100 * Vault.FEE_MULTIPLIER,
"Invalid management fee"
);
// We are dividing annualized management fee by num weeks in a year
uint256 tmpManagementFee =
newManagementFee.mul(Vault.FEE_MULTIPLIER).div(WEEKS_PER_YEAR);
emit ManagementFeeSet(managementFee, newManagementFee);
managementFee = tmpManagementFee;
}
/**
* @notice Sets the performance fee for the vault
* @param newPerformanceFee is the performance fee (6 decimals). ex: 20 * 10 ** 6 = 20%
*/
function setPerformanceFee(uint256 newPerformanceFee) external onlyOwner {
require(
newPerformanceFee < 100 * Vault.FEE_MULTIPLIER,
"Invalid performance fee"
);
emit PerformanceFeeSet(performanceFee, newPerformanceFee);
performanceFee = newPerformanceFee;
}
/**
* @notice Sets a new cap for deposits
* @param newCap is the new cap for deposits
*/
function setCap(uint256 newCap) external onlyOwner {
require(newCap > 0, "!newCap");
ShareMath.assertUint104(newCap);
emit CapSet(vaultParams.cap, newCap);
vaultParams.cap = uint104(newCap);
}
/************************************************
* DEPOSIT & WITHDRAWALS
***********************************************/
/**
* @notice Deposits ETH into the contract and mint vault shares. Reverts if the asset is not WETH.
*/
function depositETH() external payable nonReentrant {
require(vaultParams.asset == WETH, "!WETH");
require(msg.value > 0, "!value");
_depositFor(msg.value, msg.sender);
IWETH(WETH).deposit{value: msg.value}();
}
/**
* @notice Deposits the `asset` from msg.sender.
* @param amount is the amount of `asset` to deposit
*/
function deposit(uint256 amount) external nonReentrant {
require(amount > 0, "!amount");
_depositFor(amount, msg.sender);
// An approve() by the msg.sender is required beforehand
IERC20(vaultParams.asset).safeTransferFrom(
msg.sender,
address(this),
amount
);
}
/**
* @notice Deposits the `asset` from msg.sender added to `creditor`'s deposit.
* @notice Used for vault -> vault deposits on the user's behalf
* @param amount is the amount of `asset` to deposit
* @param creditor is the address that can claim/withdraw deposited amount
*/
function depositFor(uint256 amount, address creditor)
external
nonReentrant
{
require(amount > 0, "!amount");
require(creditor != address(0));
_depositFor(amount, creditor);
// An approve() by the msg.sender is required beforehand
IERC20(vaultParams.asset).safeTransferFrom(
msg.sender,
address(this),
amount
);
}
/**
* @notice Mints the vault shares to the creditor
* @param amount is the amount of `asset` deposited
* @param creditor is the address to receieve the deposit
*/
function _depositFor(uint256 amount, address creditor) private {
uint256 currentRound = vaultState.round;
uint256 totalWithDepositedAmount = totalBalance().add(amount);
require(totalWithDepositedAmount <= vaultParams.cap, "Exceed cap");
require(
totalWithDepositedAmount >= vaultParams.minimumSupply,
"Insufficient balance"
);
emit Deposit(creditor, amount, currentRound);
Vault.DepositReceipt memory depositReceipt = depositReceipts[creditor];
// If we have an unprocessed pending deposit from the previous rounds, we have to process it.
uint256 unredeemedShares =
depositReceipt.getSharesFromReceipt(
currentRound,
roundPricePerShare[depositReceipt.round],
vaultParams.decimals
);
uint256 depositAmount = amount;
// If we have a pending deposit in the current round, we add on to the pending deposit
if (currentRound == depositReceipt.round) {
uint256 newAmount = uint256(depositReceipt.amount).add(amount);
depositAmount = newAmount;
}
ShareMath.assertUint104(depositAmount);
depositReceipts[creditor] = Vault.DepositReceipt({
round: uint16(currentRound),
amount: uint104(depositAmount),
unredeemedShares: uint128(unredeemedShares)
});
uint256 newTotalPending = uint256(vaultState.totalPending).add(amount);
ShareMath.assertUint128(newTotalPending);
vaultState.totalPending = uint128(newTotalPending);
}
/**
* @notice Initiates a withdrawal that can be processed once the round completes
* @param numShares is the number of shares to withdraw
*/
function _initiateWithdraw(uint256 numShares) internal {
require(numShares > 0, "!numShares");
// We do a max redeem before initiating a withdrawal
// But we check if they must first have unredeemed shares
if (
depositReceipts[msg.sender].amount > 0 ||
depositReceipts[msg.sender].unredeemedShares > 0
) {
_redeem(0, true);
}
// This caches the `round` variable used in shareBalances
uint256 currentRound = vaultState.round;
Vault.Withdrawal storage withdrawal = withdrawals[msg.sender];
bool withdrawalIsSameRound = withdrawal.round == currentRound;
emit InitiateWithdraw(msg.sender, numShares, currentRound);
uint256 existingShares = uint256(withdrawal.shares);
uint256 withdrawalShares;
if (withdrawalIsSameRound) {
withdrawalShares = existingShares.add(numShares);
} else {
require(existingShares == 0, "Existing withdraw");
withdrawalShares = numShares;
withdrawals[msg.sender].round = uint16(currentRound);
}
ShareMath.assertUint128(withdrawalShares);
withdrawals[msg.sender].shares = uint128(withdrawalShares);
_transfer(msg.sender, address(this), numShares);
}
/**
* @notice Completes a scheduled withdrawal from a past round. Uses finalized pps for the round
* @return withdrawAmount the current withdrawal amount
*/
function _completeWithdraw() internal returns (uint256) {
Vault.Withdrawal storage withdrawal = withdrawals[msg.sender];
uint256 withdrawalShares = withdrawal.shares;
uint256 withdrawalRound = withdrawal.round;
// This checks if there is a withdrawal
require(withdrawalShares > 0, "Not initiated");
require(withdrawalRound < vaultState.round, "Round not closed");
// We leave the round number as non-zero to save on gas for subsequent writes
withdrawals[msg.sender].shares = 0;
vaultState.queuedWithdrawShares = uint128(
uint256(vaultState.queuedWithdrawShares).sub(withdrawalShares)
);
uint256 withdrawAmount =
ShareMath.sharesToAsset(
withdrawalShares,
roundPricePerShare[withdrawalRound],
vaultParams.decimals
);
emit Withdraw(msg.sender, withdrawAmount, withdrawalShares);
_burn(address(this), withdrawalShares);
require(withdrawAmount > 0, "!withdrawAmount");
transferAsset(msg.sender, withdrawAmount);
return withdrawAmount;
}
/**
* @notice Redeems shares that are owed to the account
* @param numShares is the number of shares to redeem
*/
function redeem(uint256 numShares) external nonReentrant {
require(numShares > 0, "!numShares");
_redeem(numShares, false);
}
/**
* @notice Redeems the entire unredeemedShares balance that is owed to the account
*/
function maxRedeem() external nonReentrant {
_redeem(0, true);
}
/**
* @notice Redeems shares that are owed to the account
* @param numShares is the number of shares to redeem, could be 0 when isMax=true
* @param isMax is flag for when callers do a max redemption
*/
function _redeem(uint256 numShares, bool isMax) internal {
Vault.DepositReceipt memory depositReceipt =
depositReceipts[msg.sender];
// This handles the null case when depositReceipt.round = 0
// Because we start with round = 1 at `initialize`
uint256 currentRound = vaultState.round;
uint256 unredeemedShares =
depositReceipt.getSharesFromReceipt(
currentRound,
roundPricePerShare[depositReceipt.round],
vaultParams.decimals
);
numShares = isMax ? unredeemedShares : numShares;
if (numShares == 0) {
return;
}
require(numShares <= unredeemedShares, "Exceeds available");
// If we have a depositReceipt on the same round, BUT we have some unredeemed shares
// we debit from the unredeemedShares, but leave the amount field intact
// If the round has past, with no new deposits, we just zero it out for new deposits.
if (depositReceipt.round < currentRound) {
depositReceipts[msg.sender].amount = 0;
}
ShareMath.assertUint128(numShares);
depositReceipts[msg.sender].unredeemedShares = uint128(
unredeemedShares.sub(numShares)
);
emit Redeem(msg.sender, numShares, depositReceipt.round);
_transfer(address(this), msg.sender, numShares);
}
/************************************************
* VAULT OPERATIONS
***********************************************/
/**
* @notice Helper function that helps to save gas for writing values into the roundPricePerShare map.
* Writing `1` into the map makes subsequent writes warm, reducing the gas from 20k to 5k.
* Having 1 initialized beforehand will not be an issue as long as we round down share calculations to 0.
* @param numRounds is the number of rounds to initialize in the map
*/
function initRounds(uint256 numRounds) external nonReentrant {
require(numRounds > 0, "!numRounds");
uint256 _round = vaultState.round;
for (uint256 i = 0; i < numRounds; i++) {
uint256 index = _round + i;
require(roundPricePerShare[index] == 0, "Initialized"); // AVOID OVERWRITING ACTUAL VALUES
roundPricePerShare[index] = ShareMath.PLACEHOLDER_UINT;
}
}
/**
* @notice Helper function that performs most administrative tasks
* such as setting next option, minting new shares, getting vault fees, etc.
* @param lastQueuedWithdrawAmount is old queued withdraw amount
* @param currentQueuedWithdrawShares is the queued withdraw shares for the current round
* @return newOption is the new option address
* @return lockedBalance is the new balance used to calculate next option purchase size or collateral size
* @return queuedWithdrawAmount is the new queued withdraw amount for this round
*/
function _rollToNextOption(
uint256 lastQueuedWithdrawAmount,
uint256 currentQueuedWithdrawShares
)
internal
returns (
address newOption,
uint256 lockedBalance,
uint256 queuedWithdrawAmount
)
{
require(block.timestamp >= optionState.nextOptionReadyAt, "!ready");
newOption = optionState.nextOption;
require(newOption != address(0), "!nextOption");
address recipient = feeRecipient;
uint256 mintShares;
uint256 performanceFeeInAsset;
uint256 totalVaultFee;
{
uint256 newPricePerShare;
(
lockedBalance,
queuedWithdrawAmount,
newPricePerShare,
mintShares,
performanceFeeInAsset,
totalVaultFee
) = VaultLifecycle.rollover(
vaultState,
VaultLifecycle.RolloverParams(
vaultParams.decimals,
IERC20(vaultParams.asset).balanceOf(address(this)),
totalSupply(),
lastQueuedWithdrawAmount,
performanceFee,
managementFee,
currentQueuedWithdrawShares
)
);
optionState.currentOption = newOption;
optionState.nextOption = address(0);
// Finalize the pricePerShare at the end of the round
uint256 currentRound = vaultState.round;
roundPricePerShare[currentRound] = newPricePerShare;
emit CollectVaultFees(
performanceFeeInAsset,
totalVaultFee,
currentRound,
recipient
);
vaultState.totalPending = 0;
vaultState.round = uint16(currentRound + 1);
}
_mint(address(this), mintShares);
if (totalVaultFee > 0) {
transferAsset(payable(recipient), totalVaultFee);
}
return (newOption, lockedBalance, queuedWithdrawAmount);
}
/**
* @notice Helper function to make either an ETH transfer or ERC20 transfer
* @param recipient is the receiving address
* @param amount is the transfer amount
*/
function transferAsset(address recipient, uint256 amount) internal {
address asset = vaultParams.asset;
if (asset == WETH) {
IWETH(WETH).withdraw(amount);
(bool success, ) = recipient.call{value: amount}("");
require(success, "Transfer failed");
return;
}
IERC20(asset).safeTransfer(recipient, amount);
}
/************************************************
* GETTERS
***********************************************/
/**
* @notice Returns the asset balance held on the vault for the account
* @param account is the address to lookup balance for
* @return the amount of `asset` custodied by the vault for the user
*/
function accountVaultBalance(address account)
external
view
returns (uint256)
{
uint256 _decimals = vaultParams.decimals;
uint256 assetPerShare =
ShareMath.pricePerShare(
totalSupply(),
totalBalance(),
vaultState.totalPending,
_decimals
);
return
ShareMath.sharesToAsset(shares(account), assetPerShare, _decimals);
}
/**
* @notice Getter for returning the account's share balance including unredeemed shares
* @param account is the account to lookup share balance for
* @return the share balance
*/
function shares(address account) public view returns (uint256) {
(uint256 heldByAccount, uint256 heldByVault) = shareBalances(account);
return heldByAccount.add(heldByVault);
}
/**
* @notice Getter for returning the account's share balance split between account and vault holdings
* @param account is the account to lookup share balance for
* @return heldByAccount is the shares held by account
* @return heldByVault is the shares held on the vault (unredeemedShares)
*/
function shareBalances(address account)
public
view
returns (uint256 heldByAccount, uint256 heldByVault)
{
Vault.DepositReceipt memory depositReceipt = depositReceipts[account];
if (depositReceipt.round < ShareMath.PLACEHOLDER_UINT) {
return (balanceOf(account), 0);
}
uint256 unredeemedShares =
depositReceipt.getSharesFromReceipt(
vaultState.round,
roundPricePerShare[depositReceipt.round],
vaultParams.decimals
);
return (balanceOf(account), unredeemedShares);
}
/**
* @notice The price of a unit of share denominated in the `asset`
*/
function pricePerShare() external view returns (uint256) {
return
ShareMath.pricePerShare(
totalSupply(),
totalBalance(),
vaultState.totalPending,
vaultParams.decimals
);
}
/**
* @notice Returns the vault's total balance, including the amounts locked into a short position
* @return total balance of the vault, including the amounts locked in third party protocols
*/
function totalBalance() public view returns (uint256) {
return
uint256(vaultState.lockedAmount).add(
IERC20(vaultParams.asset).balanceOf(address(this))
);
}
/**
* @notice Returns the token decimals
*/
function decimals() public view override returns (uint8) {
return vaultParams.decimals;
}
function cap() external view returns (uint256) {
return vaultParams.cap;
}
function nextOptionReadyAt() external view returns (uint256) {
return optionState.nextOptionReadyAt;
}
function currentOption() external view returns (address) {
return optionState.currentOption;
}
function nextOption() external view returns (address) {
return optionState.nextOption;
}
function totalPending() external view returns (uint256) {
return vaultState.totalPending;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize, which returns 0 for contracts in
// construction, since the code is only stored at the end of the
// constructor execution.
uint256 size;
assembly {
size := extcodesize(account)
}
return size > 0;
}
/**
* @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://diligence.consensys.net/posts/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.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @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, it is bubbled up by this
* function (like regular Solidity function calls).
*
* 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.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCall(target, data, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @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`.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
require(isContract(target), "Address: call to non-contract");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
require(isContract(target), "Address: static call to non-contract");
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
require(isContract(target), "Address: delegate call to non-contract");
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
// 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
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
// SPDX-License-Identifier: MIT
/// math.sol -- mixin for inline numerical wizardry
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
pragma solidity >0.4.13;
library DSMath {
function add(uint256 x, uint256 y) internal pure returns (uint256 z) {
require((z = x + y) >= x, "ds-math-add-overflow");
}
function sub(uint256 x, uint256 y) internal pure returns (uint256 z) {
require((z = x - y) <= x, "ds-math-sub-underflow");
}
function mul(uint256 x, uint256 y) internal pure returns (uint256 z) {
require(y == 0 || (z = x * y) / y == x, "ds-math-mul-overflow");
}
function min(uint256 x, uint256 y) internal pure returns (uint256 z) {
return x <= y ? x : y;
}
function max(uint256 x, uint256 y) internal pure returns (uint256 z) {
return x >= y ? x : y;
}
function imin(int256 x, int256 y) internal pure returns (int256 z) {
return x <= y ? x : y;
}
function imax(int256 x, int256 y) internal pure returns (int256 z) {
return x >= y ? x : y;
}
uint256 constant WAD = 10**18;
uint256 constant RAY = 10**27;
//rounds to zero if x*y < WAD / 2
function wmul(uint256 x, uint256 y) internal pure returns (uint256 z) {
z = add(mul(x, y), WAD / 2) / WAD;
}
//rounds to zero if x*y < WAD / 2
function rmul(uint256 x, uint256 y) internal pure returns (uint256 z) {
z = add(mul(x, y), RAY / 2) / RAY;
}
//rounds to zero if x*y < WAD / 2
function wdiv(uint256 x, uint256 y) internal pure returns (uint256 z) {
z = add(mul(x, WAD), y / 2) / y;
}
//rounds to zero if x*y < RAY / 2
function rdiv(uint256 x, uint256 y) internal pure returns (uint256 z) {
z = add(mul(x, RAY), y / 2) / y;
}
// This famous algorithm is called "exponentiation by squaring"
// and calculates x^n with x as fixed-point and n as regular unsigned.
//
// It's O(log n), instead of O(n) for naive repeated multiplication.
//
// These facts are why it works:
//
// If n is even, then x^n = (x^2)^(n/2).
// If n is odd, then x^n = x * x^(n-1),
// and applying the equation for even x gives
// x^n = x * (x^2)^((n-1) / 2).
//
// Also, EVM division is flooring and
// floor[(n-1) / 2] = floor[n / 2].
//
function rpow(uint256 x, uint256 n) internal pure returns (uint256 z) {
z = n % 2 != 0 ? x : RAY;
for (n /= 2; n != 0; n /= 2) {
x = rmul(x, x);
if (n % 2 != 0) {
z = rmul(z, x);
}
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity =0.8.4;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
library AuctionType {
struct AuctionData {
IERC20 auctioningToken;
IERC20 biddingToken;
uint256 orderCancellationEndDate;
uint256 auctionEndDate;
bytes32 initialAuctionOrder;
uint256 minimumBiddingAmountPerOrder;
uint256 interimSumBidAmount;
bytes32 interimOrder;
bytes32 clearingPriceOrder;
uint96 volumeClearingPriceOrder;
bool minFundingThresholdNotReached;
bool isAtomicClosureAllowed;
uint256 feeNumerator;
uint256 minFundingThreshold;
}
}
interface IGnosisAuction {
function initiateAuction(
address _auctioningToken,
address _biddingToken,
uint256 orderCancellationEndDate,
uint256 auctionEndDate,
uint96 _auctionedSellAmount,
uint96 _minBuyAmount,
uint256 minimumBiddingAmountPerOrder,
uint256 minFundingThreshold,
bool isAtomicClosureAllowed,
address accessManagerContract,
bytes memory accessManagerContractData
) external returns (uint256);
function auctionCounter() external view returns (uint256);
function auctionData(uint256 auctionId)
external
view
returns (AuctionType.AuctionData memory);
function auctionAccessManager(uint256 auctionId)
external
view
returns (address);
function auctionAccessData(uint256 auctionId)
external
view
returns (bytes memory);
function FEE_DENOMINATOR() external view returns (uint256);
function feeNumerator() external view returns (uint256);
function settleAuction(uint256 auctionId) external returns (bytes32);
function placeSellOrders(
uint256 auctionId,
uint96[] memory _minBuyAmounts,
uint96[] memory _sellAmounts,
bytes32[] memory _prevSellOrders,
bytes calldata allowListCallData
) external returns (uint64);
function claimFromParticipantOrder(
uint256 auctionId,
bytes32[] memory orders
) external returns (uint256, uint256);
}
// SPDX-License-Identifier: MIT
pragma solidity =0.8.4;
library GammaTypes {
// vault is a struct of 6 arrays that describe a position a user has, a user can have multiple vaults.
struct Vault {
// addresses of oTokens a user has shorted (i.e. written) against this vault
address[] shortOtokens;
// addresses of oTokens a user has bought and deposited in this vault
// user can be long oTokens without opening a vault (e.g. by buying on a DEX)
// generally, long oTokens will be 'deposited' in vaults to act as collateral
// in order to write oTokens against (i.e. in spreads)
address[] longOtokens;
// addresses of other ERC-20s a user has deposited as collateral in this vault
address[] collateralAssets;
// quantity of oTokens minted/written for each oToken address in shortOtokens
uint256[] shortAmounts;
// quantity of oTokens owned and held in the vault for each oToken address in longOtokens
uint256[] longAmounts;
// quantity of ERC-20 deposited as collateral in the vault for each ERC-20 address in collateralAssets
uint256[] collateralAmounts;
}
}
interface IOtoken {
function underlyingAsset() external view returns (address);
function strikeAsset() external view returns (address);
function collateralAsset() external view returns (address);
function strikePrice() external view returns (uint256);
function expiryTimestamp() external view returns (uint256);
function isPut() external view returns (bool);
}
interface IOtokenFactory {
function getOtoken(
address _underlyingAsset,
address _strikeAsset,
address _collateralAsset,
uint256 _strikePrice,
uint256 _expiry,
bool _isPut
) external view returns (address);
function createOtoken(
address _underlyingAsset,
address _strikeAsset,
address _collateralAsset,
uint256 _strikePrice,
uint256 _expiry,
bool _isPut
) external returns (address);
function getTargetOtokenAddress(
address _underlyingAsset,
address _strikeAsset,
address _collateralAsset,
uint256 _strikePrice,
uint256 _expiry,
bool _isPut
) external view returns (address);
event OtokenCreated(
address tokenAddress,
address creator,
address indexed underlying,
address indexed strike,
address indexed collateral,
uint256 strikePrice,
uint256 expiry,
bool isPut
);
}
interface IController {
// possible actions that can be performed
enum ActionType {
OpenVault,
MintShortOption,
BurnShortOption,
DepositLongOption,
WithdrawLongOption,
DepositCollateral,
WithdrawCollateral,
SettleVault,
Redeem,
Call,
Liquidate
}
struct ActionArgs {
// type of action that is being performed on the system
ActionType actionType;
// address of the account owner
address owner;
// address which we move assets from or to (depending on the action type)
address secondAddress;
// asset that is to be transfered
address asset;
// index of the vault that is to be modified (if any)
uint256 vaultId;
// amount of asset that is to be transfered
uint256 amount;
// each vault can hold multiple short / long / collateral assets
// but we are restricting the scope to only 1 of each in this version
// in future versions this would be the index of the short / long / collateral asset that needs to be modified
uint256 index;
// any other data that needs to be passed in for arbitrary function calls
bytes data;
}
struct RedeemArgs {
// address to which we pay out the oToken proceeds
address receiver;
// oToken that is to be redeemed
address otoken;
// amount of oTokens that is to be redeemed
uint256 amount;
}
function getPayout(address _otoken, uint256 _amount)
external
view
returns (uint256);
function operate(ActionArgs[] calldata _actions) external;
function getAccountVaultCounter(address owner)
external
view
returns (uint256);
function oracle() external view returns (address);
function getVault(address _owner, uint256 _vaultId)
external
view
returns (GammaTypes.Vault memory);
function getProceed(address _owner, uint256 _vaultId)
external
view
returns (uint256);
function isSettlementAllowed(
address _underlying,
address _strike,
address _collateral,
uint256 _expiry
) external view returns (bool);
}
interface IOracle {
function setAssetPricer(address _asset, address _pricer) external;
function updateAssetPricer(address _asset, address _pricer) external;
function getPrice(address _asset) external view returns (uint256);
}
// SPDX-License-Identifier: MIT
pragma solidity =0.8.4;
import {Vault} from "../libraries/Vault.sol";
interface IRibbonVault {
function deposit(uint256 amount) external;
function depositETH() external payable;
function cap() external view returns (uint256);
function depositFor(uint256 amount, address creditor) external;
function vaultParams() external view returns (Vault.VaultParams memory);
}
interface IStrikeSelection {
function getStrikePrice(uint256 expiryTimestamp, bool isPut)
external
view
returns (uint256, uint256);
function delta() external view returns (uint256);
}
interface IOptionsPremiumPricer {
function getPremium(
uint256 strikePrice,
uint256 timeToExpiry,
bool isPut
) external view returns (uint256);
function getPremiumInStables(
uint256 strikePrice,
uint256 timeToExpiry,
bool isPut
) external view returns (uint256);
function getOptionDelta(
uint256 spotPrice,
uint256 strikePrice,
uint256 volatility,
uint256 expiryTimestamp
) external view returns (uint256 delta);
function getUnderlyingPrice() external view returns (uint256);
function priceOracle() external view returns (address);
function volatilityOracle() external view returns (address);
function optionId() external view returns (bytes32);
}
// SPDX-License-Identifier: MIT
pragma solidity =0.8.4;
import {Vault} from "../libraries/Vault.sol";
interface IRibbonThetaVault {
function currentOption() external view returns (address);
function nextOption() external view returns (address);
function vaultParams() external view returns (Vault.VaultParams memory);
function vaultState() external view returns (Vault.VaultState memory);
function optionState() external view returns (Vault.OptionState memory);
function optionAuctionID() external view returns (uint256);
function pricePerShare() external view returns (uint256);
function roundPricePerShare(uint256) external view returns (uint256);
function depositFor(uint256 amount, address creditor) external;
function initiateWithdraw(uint256 numShares) external;
function completeWithdraw() external;
function maxRedeem() external;
function depositYieldTokenFor(uint256 amount, address creditor) external;
function symbol() external view returns (string calldata);
}
// SPDX-License-Identifier: MIT
pragma solidity =0.8.4;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IERC20Detailed is IERC20 {
function decimals() external view returns (uint8);
function symbol() external view returns (string calldata);
function name() external view returns (string calldata);
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
interface IOptionsPurchaseQueue {
/**
* @dev Contains purchase request info
* @param optionsAmount Amount of options to purchase
* @param premiums Total premiums the buyer is spending to purchase the options (optionsAmount * ceilingPrice)
* We need to track the premiums here since the ceilingPrice could change between the time the purchase was
* requested and when the options are sold
* @param buyer The buyer requesting this purchase
*/
struct Purchase {
uint128 optionsAmount; // Slot 0
uint128 premiums;
address buyer; // Slot 1
}
function purchases(address, uint256)
external
view
returns (
uint128,
uint128,
address
);
function totalOptionsAmount(address) external view returns (uint256);
function vaultAllocatedOptions(address) external view returns (uint256);
function whitelistedBuyer(address) external view returns (bool);
function minPurchaseAmount(address) external view returns (uint256);
function ceilingPrice(address) external view returns (uint256);
function getPurchases(address vault)
external
view
returns (Purchase[] memory);
function getPremiums(address vault, uint256 optionsAmount)
external
view
returns (uint256);
function getOptionsAllocation(address vault, uint256 allocatedOptions)
external
view
returns (uint256);
function requestPurchase(address vault, uint256 optionsAmount)
external
returns (uint256);
function allocateOptions(uint256 allocatedOptions)
external
returns (uint256);
function sellToBuyers(uint256 settlementPrice) external returns (uint256);
function cancelAllPurchases(address vault) external;
function addWhitelist(address buyer) external;
function removeWhitelist(address buyer) external;
function setCeilingPrice(address vault, uint256 price) external;
function setMinPurchaseAmount(address vault, uint256 amount) external;
}
// SPDX-License-Identifier: MIT
pragma solidity =0.8.4;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {
SafeERC20
} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
/**
* This library supports ERC20s that have quirks in their behavior.
* One such ERC20 is USDT, which requires allowance to be 0 before calling approve.
* We plan to update this library with ERC20s that display such idiosyncratic behavior.
*/
library SupportsNonCompliantERC20 {
address private constant USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7;
function safeApproveNonCompliant(
IERC20 token,
address spender,
uint256 amount
) internal {
if (address(token) == USDT) {
SafeERC20.safeApprove(token, spender, 0);
}
SafeERC20.safeApprove(token, spender, amount);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../proxy/utils/Initializable.sol";
/**
* @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 ReentrancyGuardUpgradeable is Initializable {
// 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;
function __ReentrancyGuard_init() internal initializer {
__ReentrancyGuard_init_unchained();
}
function __ReentrancyGuard_init_unchained() internal initializer {
_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 make it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
// On the first call to nonReentrant, _notEntered will be true
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
_;
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
uint256[49] private __gap;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../utils/ContextUpgradeable.sol";
import "../proxy/utils/Initializable.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
function __Ownable_init() internal initializer {
__Context_init_unchained();
__Ownable_init_unchained();
}
function __Ownable_init_unchained() internal initializer {
_setOwner(_msgSender());
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
_;
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_setOwner(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_setOwner(newOwner);
}
function _setOwner(address newOwner) private {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
uint256[49] private __gap;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./IERC20Upgradeable.sol";
import "./extensions/IERC20MetadataUpgradeable.sol";
import "../../utils/ContextUpgradeable.sol";
import "../../proxy/utils/Initializable.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}.
* For a generic mechanism see {ERC20PresetMinterPauser}.
*
* TIP: For a detailed writeup see our guide
* https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* 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.
*
* Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
* functions have been added to mitigate the well-known issues around setting
* allowances. See {IERC20-approve}.
*/
contract ERC20Upgradeable is Initializable, ContextUpgradeable, IERC20Upgradeable, IERC20MetadataUpgradeable {
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* The default value of {decimals} is 18. To select a different value for
* {decimals} you should overload it.
*
* All two of these values are immutable: they can only be set once during
* construction.
*/
function __ERC20_init(string memory name_, string memory symbol_) internal initializer {
__Context_init_unchained();
__ERC20_init_unchained(name_, symbol_);
}
function __ERC20_init_unchained(string memory name_, string memory symbol_) internal initializer {
_name = name_;
_symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual override returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual override 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 value {ERC20} uses, unless this function is
* 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 override returns (uint8) {
return 18;
}
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view virtual override returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view virtual override returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `recipient` cannot be the zero address.
* - the caller must have a balance of at least `amount`.
*/
function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
_transfer(_msgSender(), recipient, amount);
return true;
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender) public view virtual override returns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 amount) public virtual override returns (bool) {
_approve(_msgSender(), spender, amount);
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}.
*
* Requirements:
*
* - `sender` and `recipient` cannot be the zero address.
* - `sender` must have a balance of at least `amount`.
* - the caller must have allowance for ``sender``'s tokens of at least
* `amount`.
*/
function transferFrom(
address sender,
address recipient,
uint256 amount
) public virtual override returns (bool) {
_transfer(sender, recipient, amount);
uint256 currentAllowance = _allowances[sender][_msgSender()];
require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");
unchecked {
_approve(sender, _msgSender(), currentAllowance - amount);
}
return true;
}
/**
* @dev Atomically increases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
_approve(_msgSender(), spender, _allowances[_msgSender()][spender] + addedValue);
return true;
}
/**
* @dev Atomically decreases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `spender` must have allowance for the caller of at least
* `subtractedValue`.
*/
function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
uint256 currentAllowance = _allowances[_msgSender()][spender];
require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
unchecked {
_approve(_msgSender(), spender, currentAllowance - subtractedValue);
}
return true;
}
/**
* @dev Moves `amount` of tokens from `sender` to `recipient`.
*
* 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.
*
* Requirements:
*
* - `sender` cannot be the zero address.
* - `recipient` cannot be the zero address.
* - `sender` must have a balance of at least `amount`.
*/
function _transfer(
address sender,
address recipient,
uint256 amount
) internal virtual {
require(sender != address(0), "ERC20: transfer from the zero address");
require(recipient != address(0), "ERC20: transfer to the zero address");
_beforeTokenTransfer(sender, recipient, amount);
uint256 senderBalance = _balances[sender];
require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");
unchecked {
_balances[sender] = senderBalance - amount;
}
_balances[recipient] += amount;
emit Transfer(sender, recipient, amount);
_afterTokenTransfer(sender, recipient, amount);
}
/** @dev Creates `amount` tokens and assigns them to `account`, increasing
* the total supply.
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
*/
function _mint(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: mint to the zero address");
_beforeTokenTransfer(address(0), account, amount);
_totalSupply += amount;
_balances[account] += amount;
emit Transfer(address(0), account, amount);
_afterTokenTransfer(address(0), account, amount);
}
/**
* @dev Destroys `amount` tokens from `account`, reducing the
* total supply.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
* - `account` must have at least `amount` tokens.
*/
function _burn(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: burn from the zero address");
_beforeTokenTransfer(account, address(0), amount);
uint256 accountBalance = _balances[account];
require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
unchecked {
_balances[account] = accountBalance - amount;
}
_totalSupply -= amount;
emit Transfer(account, address(0), amount);
_afterTokenTransfer(account, address(0), amount);
}
/**
* @dev Sets `amount` 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.
*/
function _approve(
address owner,
address spender,
uint256 amount
) internal virtual {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
/**
* @dev Hook that is called before any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* will be transferred to `to`.
* - when `from` is zero, `amount` tokens will be minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens will be burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeTokenTransfer(
address from,
address to,
uint256 amount
) internal virtual {}
/**
* @dev Hook that is called after any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* has been transferred to `to`.
* - when `from` is zero, `amount` tokens have been minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens have been burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _afterTokenTransfer(
address from,
address to,
uint256 amount
) internal virtual {}
uint256[45] private __gap;
}
// SPDX-License-Identifier: MIT
pragma solidity =0.8.4;
interface IWETH {
function deposit() external payable;
function withdraw(uint256) external;
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount)
external
returns (bool);
function allowance(address owner, address spender)
external
view
returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
function decimals() external view returns (uint256);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since a proxied contract can't have a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*/
abstract contract Initializable {
/**
* @dev Indicates that the contract has been initialized.
*/
bool private _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool private _initializing;
/**
* @dev Modifier to protect an initializer function from being invoked twice.
*/
modifier initializer() {
require(_initializing || !_initialized, "Initializable: contract is already initialized");
bool isTopLevelCall = !_initializing;
if (isTopLevelCall) {
_initializing = true;
_initialized = true;
}
_;
if (isTopLevelCall) {
_initializing = false;
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../proxy/utils/Initializable.sol";
/**
* @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 ContextUpgradeable is Initializable {
function __Context_init() internal initializer {
__Context_init_unchained();
}
function __Context_init_unchained() internal initializer {
}
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
uint256[50] private __gap;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20Upgradeable {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount) 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 `amount` 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 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
/**
* @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);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../IERC20Upgradeable.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*
* _Available since v4.1._
*/
interface IERC20MetadataUpgradeable is IERC20Upgradeable {
/**
* @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);
}