Skip to content

Instantly share code, notes, and snippets.

@haythemsellami
Last active June 24, 2021 22:30
Show Gist options
  • Save haythemsellami/1993729a1bf9992ad8629e1753a93b4e to your computer and use it in GitHub Desktop.
Save haythemsellami/1993729a1bf9992ad8629e1753a93b4e to your computer and use it in GitHub Desktop.
Flattened Controller.sol v2.0.0
/**
* SPDX-License-Identifier: UNLICENSED
*/
pragma solidity =0.6.10;
pragma experimental ABIEncoderV2;
// File: contracts/packages/oz/upgradeability/Initializable.sol
/**
* @title Initializable
*
* @dev Helper contract to support initializer functions. To use it, replace
* the constructor with a function that has the `initializer` modifier.
* WARNING: Unlike constructors, initializer functions must be manually
* invoked. This applies both to deploying an Initializable contract, as well
* as extending an Initializable contract via inheritance.
* WARNING: When used with inheritance, manual care must be taken to not invoke
* a parent initializer twice, or ensure that all initializers are idempotent,
* because this is not dealt with automatically as with constructors.
*/
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 use in the initializer function of a contract.
*/
modifier initializer() {
require(initializing || isConstructor() || !initialized, "Contract instance has already been initialized");
bool isTopLevelCall = !initializing;
if (isTopLevelCall) {
initializing = true;
initialized = true;
}
_;
if (isTopLevelCall) {
initializing = false;
}
}
/// @dev Returns true if and only if the function is running in the constructor
function isConstructor() private view returns (bool) {
// extcodesize checks the size of the code stored in an address, and
// address returns the current address. Since the code is still not
// deployed when running a constructor, any checks on its code size will
// yield zero, making it an effective way to detect if a contract is
// under construction or not.
address self = address(this);
uint256 cs;
assembly {
cs := extcodesize(self)
}
return cs == 0;
}
// Reserved storage space to allow for layout changes in the future.
uint256[50] private ______gap;
}
// File: contracts/packages/oz/upgradeability/GSN/ContextUpgradeable.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 GSN 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 virtual view returns (address payable) {
return msg.sender;
}
function _msgData() internal virtual view returns (bytes memory) {
this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
return msg.data;
}
uint256[50] private __gap;
}
// File: contracts/packages/oz/upgradeability/OwnableUpgradeSafe.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.
*/
contract OwnableUpgradeSafe 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(address _sender) internal initializer {
__Context_init_unchained();
__Ownable_init_unchained(_sender);
}
function __Ownable_init_unchained(address _sender) internal initializer {
_owner = _sender;
emit OwnershipTransferred(address(0), _sender);
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view 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 {
emit OwnershipTransferred(_owner, address(0));
_owner = 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");
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
uint256[49] private __gap;
}
// File: contracts/packages/oz/upgradeability/ReentrancyGuardUpgradeSafe.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].
*/
contract ReentrancyGuardUpgradeSafe is Initializable {
bool private _notEntered;
function __ReentrancyGuard_init() internal initializer {
__ReentrancyGuard_init_unchained();
}
function __ReentrancyGuard_init_unchained() internal initializer {
// Storing an initial 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 percetange 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.
_notEntered = true;
}
/**
* @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(_notEntered, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_notEntered = false;
_;
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_notEntered = true;
}
uint256[49] private __gap;
}
// File: contracts/packages/oz/SafeMath.sol
/**
* @dev Wrappers over Solidity's arithmetic operations with added overflow
* checks.
*
* Arithmetic operations in Solidity wrap on overflow. This can easily result
* in bugs, because programmers usually assume that an overflow raises an
* error, which is the standard behavior in high level programming languages.
* `SafeMath` restores this intuition by reverting the transaction when an
* operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/
library SafeMath {
/**
* @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) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
/**
* @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 sub(a, b, "SafeMath: subtraction overflow");
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
* - Subtraction cannot overflow.
*/
function sub(
uint256 a,
uint256 b,
string memory errorMessage
) internal pure returns (uint256) {
require(b <= a, errorMessage);
uint256 c = a - b;
return c;
}
/**
* @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) {
// 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 0;
}
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts 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) internal pure returns (uint256) {
return div(a, b, "SafeMath: division by zero");
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts 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) {
// Solidity only automatically asserts when dividing by 0
require(b > 0, errorMessage);
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts 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 mod(a, b, "SafeMath: modulo by zero");
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts with custom message 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,
string memory errorMessage
) internal pure returns (uint256) {
require(b != 0, errorMessage);
return a % b;
}
}
// File: contracts/libs/MarginVault.sol
/**
* MarginVault Error Codes
* V1: invalid short otoken amount
* V2: invalid short otoken index
* V3: short otoken address mismatch
* V4: invalid long otoken amount
* V5: invalid long otoken index
* V6: long otoken address mismatch
* V7: invalid collateral amount
* V8: invalid collateral token index
* V9: collateral token address mismatch
*/
/**
* @title MarginVault
* @author Opyn Team
* @notice A library that provides the Controller with a Vault struct and the functions that manipulate vaults.
* Vaults describe discrete position combinations of long options, short options, and collateral assets that a user can have.
*/
library MarginVault {
using SafeMath for uint256;
// 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;
}
/**
* @dev increase the short oToken balance in a vault when a new oToken is minted
* @param _vault vault to add or increase the short position in
* @param _shortOtoken address of the _shortOtoken being minted from the user's vault
* @param _amount number of _shortOtoken being minted from the user's vault
* @param _index index of _shortOtoken in the user's vault.shortOtokens array
*/
function addShort(
Vault storage _vault,
address _shortOtoken,
uint256 _amount,
uint256 _index
) external {
require(_amount > 0, "V1");
// valid indexes in any array are between 0 and array.length - 1.
// if adding an amount to an preexisting short oToken, check that _index is in the range of 0->length-1
if ((_index == _vault.shortOtokens.length) && (_index == _vault.shortAmounts.length)) {
_vault.shortOtokens.push(_shortOtoken);
_vault.shortAmounts.push(_amount);
} else {
require((_index < _vault.shortOtokens.length) && (_index < _vault.shortAmounts.length), "V2");
address existingShort = _vault.shortOtokens[_index];
require((existingShort == _shortOtoken) || (existingShort == address(0)), "V3");
_vault.shortAmounts[_index] = _vault.shortAmounts[_index].add(_amount);
_vault.shortOtokens[_index] = _shortOtoken;
}
}
/**
* @dev decrease the short oToken balance in a vault when an oToken is burned
* @param _vault vault to decrease short position in
* @param _shortOtoken address of the _shortOtoken being reduced in the user's vault
* @param _amount number of _shortOtoken being reduced in the user's vault
* @param _index index of _shortOtoken in the user's vault.shortOtokens array
*/
function removeShort(
Vault storage _vault,
address _shortOtoken,
uint256 _amount,
uint256 _index
) external {
// check that the removed short oToken exists in the vault at the specified index
require(_index < _vault.shortOtokens.length, "V2");
require(_vault.shortOtokens[_index] == _shortOtoken, "V3");
uint256 newShortAmount = _vault.shortAmounts[_index].sub(_amount);
if (newShortAmount == 0) {
delete _vault.shortOtokens[_index];
}
_vault.shortAmounts[_index] = newShortAmount;
}
/**
* @dev increase the long oToken balance in a vault when an oToken is deposited
* @param _vault vault to add a long position to
* @param _longOtoken address of the _longOtoken being added to the user's vault
* @param _amount number of _longOtoken the protocol is adding to the user's vault
* @param _index index of _longOtoken in the user's vault.longOtokens array
*/
function addLong(
Vault storage _vault,
address _longOtoken,
uint256 _amount,
uint256 _index
) external {
require(_amount > 0, "V4");
// valid indexes in any array are between 0 and array.length - 1.
// if adding an amount to an preexisting short oToken, check that _index is in the range of 0->length-1
if ((_index == _vault.longOtokens.length) && (_index == _vault.longAmounts.length)) {
_vault.longOtokens.push(_longOtoken);
_vault.longAmounts.push(_amount);
} else {
require((_index < _vault.longOtokens.length) && (_index < _vault.longAmounts.length), "V5");
address existingLong = _vault.longOtokens[_index];
require((existingLong == _longOtoken) || (existingLong == address(0)), "V6");
_vault.longAmounts[_index] = _vault.longAmounts[_index].add(_amount);
_vault.longOtokens[_index] = _longOtoken;
}
}
/**
* @dev decrease the long oToken balance in a vault when an oToken is withdrawn
* @param _vault vault to remove a long position from
* @param _longOtoken address of the _longOtoken being removed from the user's vault
* @param _amount number of _longOtoken the protocol is removing from the user's vault
* @param _index index of _longOtoken in the user's vault.longOtokens array
*/
function removeLong(
Vault storage _vault,
address _longOtoken,
uint256 _amount,
uint256 _index
) external {
// check that the removed long oToken exists in the vault at the specified index
require(_index < _vault.longOtokens.length, "V5");
require(_vault.longOtokens[_index] == _longOtoken, "V6");
uint256 newLongAmount = _vault.longAmounts[_index].sub(_amount);
if (newLongAmount == 0) {
delete _vault.longOtokens[_index];
}
_vault.longAmounts[_index] = newLongAmount;
}
/**
* @dev increase the collateral balance in a vault
* @param _vault vault to add collateral to
* @param _collateralAsset address of the _collateralAsset being added to the user's vault
* @param _amount number of _collateralAsset being added to the user's vault
* @param _index index of _collateralAsset in the user's vault.collateralAssets array
*/
function addCollateral(
Vault storage _vault,
address _collateralAsset,
uint256 _amount,
uint256 _index
) external {
require(_amount > 0, "V7");
// valid indexes in any array are between 0 and array.length - 1.
// if adding an amount to an preexisting short oToken, check that _index is in the range of 0->length-1
if ((_index == _vault.collateralAssets.length) && (_index == _vault.collateralAmounts.length)) {
_vault.collateralAssets.push(_collateralAsset);
_vault.collateralAmounts.push(_amount);
} else {
require((_index < _vault.collateralAssets.length) && (_index < _vault.collateralAmounts.length), "V8");
address existingCollateral = _vault.collateralAssets[_index];
require((existingCollateral == _collateralAsset) || (existingCollateral == address(0)), "V9");
_vault.collateralAmounts[_index] = _vault.collateralAmounts[_index].add(_amount);
_vault.collateralAssets[_index] = _collateralAsset;
}
}
/**
* @dev decrease the collateral balance in a vault
* @param _vault vault to remove collateral from
* @param _collateralAsset address of the _collateralAsset being removed from the user's vault
* @param _amount number of _collateralAsset being removed from the user's vault
* @param _index index of _collateralAsset in the user's vault.collateralAssets array
*/
function removeCollateral(
Vault storage _vault,
address _collateralAsset,
uint256 _amount,
uint256 _index
) external {
// check that the removed collateral exists in the vault at the specified index
require(_index < _vault.collateralAssets.length, "V8");
require(_vault.collateralAssets[_index] == _collateralAsset, "V9");
uint256 newCollateralAmount = _vault.collateralAmounts[_index].sub(_amount);
if (newCollateralAmount == 0) {
delete _vault.collateralAssets[_index];
}
_vault.collateralAmounts[_index] = newCollateralAmount;
}
}
// File: contracts/libs/Actions.sol
/**
* @title Actions
* @author Opyn Team
* @notice A library that provides a ActionArgs struct, sub types of Action structs, and functions to parse ActionArgs into specific Actions.
* errorCode
* A1 can only parse arguments for open vault actions
* A2 cannot open vault for an invalid account
* A3 cannot open vault with an invalid type
* A4 can only parse arguments for mint actions
* A5 cannot mint from an invalid account
* A6 can only parse arguments for burn actions
* A7 cannot burn from an invalid account
* A8 can only parse arguments for deposit actions
* A9 cannot deposit to an invalid account
* A10 can only parse arguments for withdraw actions
* A11 cannot withdraw from an invalid account
* A12 cannot withdraw to an invalid account
* A13 can only parse arguments for redeem actions
* A14 cannot redeem to an invalid account
* A15 can only parse arguments for settle vault actions
* A16 cannot settle vault for an invalid account
* A17 cannot withdraw payout to an invalid account
* A18 can only parse arguments for liquidate action
* A19 cannot liquidate vault for an invalid account owner
* A20 cannot send collateral to an invalid account
* A21 cannot parse liquidate action with no round id
* A22 can only parse arguments for call actions
* A23 target address cannot be address(0)
*/
library Actions {
// 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 MintArgs {
// address of the account owner
address owner;
// index of the vault from which the asset will be minted
uint256 vaultId;
// address to which we transfer the minted oTokens
address to;
// oToken that is to be minted
address otoken;
// 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;
// amount of oTokens that is to be minted
uint256 amount;
}
struct BurnArgs {
// address of the account owner
address owner;
// index of the vault from which the oToken will be burned
uint256 vaultId;
// address from which we transfer the oTokens
address from;
// oToken that is to be burned
address otoken;
// 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;
// amount of oTokens that is to be burned
uint256 amount;
}
struct OpenVaultArgs {
// address of the account owner
address owner;
// vault id to create
uint256 vaultId;
// vault type, 0 for spread/max loss and 1 for naked margin vault
uint256 vaultType;
}
struct DepositArgs {
// address of the account owner
address owner;
// index of the vault to which the asset will be added
uint256 vaultId;
// address from which we transfer the asset
address from;
// asset that is to be deposited
address asset;
// 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;
// amount of asset that is to be deposited
uint256 amount;
}
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;
}
struct WithdrawArgs {
// address of the account owner
address owner;
// index of the vault from which the asset will be withdrawn
uint256 vaultId;
// address to which we transfer the asset
address to;
// asset that is to be withdrawn
address asset;
// 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;
// amount of asset that is to be withdrawn
uint256 amount;
}
struct SettleVaultArgs {
// address of the account owner
address owner;
// index of the vault to which is to be settled
uint256 vaultId;
// address to which we transfer the remaining collateral
address to;
}
struct LiquidateArgs {
// address of the vault owner to liquidate
address owner;
// address of the liquidated collateral receiver
address receiver;
// vault id to liquidate
uint256 vaultId;
// amount of debt(otoken) to repay
uint256 amount;
// chainlink round id
uint256 roundId;
}
struct CallArgs {
// address of the callee contract
address callee;
// data field for external calls
bytes data;
}
/**
* @notice parses the passed in action arguments to get the arguments for an open vault action
* @param _args general action arguments structure
* @return arguments for a open vault action
*/
function _parseOpenVaultArgs(ActionArgs memory _args) internal pure returns (OpenVaultArgs memory) {
require(_args.actionType == ActionType.OpenVault, "A1");
require(_args.owner != address(0), "A2");
// if not _args.data included, vault type will be 0 by default
uint256 vaultType;
if (_args.data.length == 32) {
// decode vault type from _args.data
vaultType = abi.decode(_args.data, (uint256));
}
// for now we only have 2 vault types
require(vaultType < 2, "A3");
return OpenVaultArgs({owner: _args.owner, vaultId: _args.vaultId, vaultType: vaultType});
}
/**
* @notice parses the passed in action arguments to get the arguments for a mint action
* @param _args general action arguments structure
* @return arguments for a mint action
*/
function _parseMintArgs(ActionArgs memory _args) internal pure returns (MintArgs memory) {
require(_args.actionType == ActionType.MintShortOption, "A4");
require(_args.owner != address(0), "A5");
return
MintArgs({
owner: _args.owner,
vaultId: _args.vaultId,
to: _args.secondAddress,
otoken: _args.asset,
index: _args.index,
amount: _args.amount
});
}
/**
* @notice parses the passed in action arguments to get the arguments for a burn action
* @param _args general action arguments structure
* @return arguments for a burn action
*/
function _parseBurnArgs(ActionArgs memory _args) internal pure returns (BurnArgs memory) {
require(_args.actionType == ActionType.BurnShortOption, "A6");
require(_args.owner != address(0), "A7");
return
BurnArgs({
owner: _args.owner,
vaultId: _args.vaultId,
from: _args.secondAddress,
otoken: _args.asset,
index: _args.index,
amount: _args.amount
});
}
/**
* @notice parses the passed in action arguments to get the arguments for a deposit action
* @param _args general action arguments structure
* @return arguments for a deposit action
*/
function _parseDepositArgs(ActionArgs memory _args) internal pure returns (DepositArgs memory) {
require(
(_args.actionType == ActionType.DepositLongOption) || (_args.actionType == ActionType.DepositCollateral),
"A8"
);
require(_args.owner != address(0), "A9");
return
DepositArgs({
owner: _args.owner,
vaultId: _args.vaultId,
from: _args.secondAddress,
asset: _args.asset,
index: _args.index,
amount: _args.amount
});
}
/**
* @notice parses the passed in action arguments to get the arguments for a withdraw action
* @param _args general action arguments structure
* @return arguments for a withdraw action
*/
function _parseWithdrawArgs(ActionArgs memory _args) internal pure returns (WithdrawArgs memory) {
require(
(_args.actionType == ActionType.WithdrawLongOption) || (_args.actionType == ActionType.WithdrawCollateral),
"A10"
);
require(_args.owner != address(0), "A11");
require(_args.secondAddress != address(0), "A12");
return
WithdrawArgs({
owner: _args.owner,
vaultId: _args.vaultId,
to: _args.secondAddress,
asset: _args.asset,
index: _args.index,
amount: _args.amount
});
}
/**
* @notice parses the passed in action arguments to get the arguments for an redeem action
* @param _args general action arguments structure
* @return arguments for a redeem action
*/
function _parseRedeemArgs(ActionArgs memory _args) internal pure returns (RedeemArgs memory) {
require(_args.actionType == ActionType.Redeem, "A13");
require(_args.secondAddress != address(0), "A14");
return RedeemArgs({receiver: _args.secondAddress, otoken: _args.asset, amount: _args.amount});
}
/**
* @notice parses the passed in action arguments to get the arguments for a settle vault action
* @param _args general action arguments structure
* @return arguments for a settle vault action
*/
function _parseSettleVaultArgs(ActionArgs memory _args) internal pure returns (SettleVaultArgs memory) {
require(_args.actionType == ActionType.SettleVault, "A15");
require(_args.owner != address(0), "A16");
require(_args.secondAddress != address(0), "A17");
return SettleVaultArgs({owner: _args.owner, vaultId: _args.vaultId, to: _args.secondAddress});
}
function _parseLiquidateArgs(ActionArgs memory _args) internal pure returns (LiquidateArgs memory) {
require(_args.actionType == ActionType.Liquidate, "A18");
require(_args.owner != address(0), "A19");
require(_args.secondAddress != address(0), "A20");
require(_args.data.length == 32, "A21");
// decode chainlink round id from _args.data
uint256 roundId = abi.decode(_args.data, (uint256));
return
LiquidateArgs({
owner: _args.owner,
receiver: _args.secondAddress,
vaultId: _args.vaultId,
amount: _args.amount,
roundId: roundId
});
}
/**
* @notice parses the passed in action arguments to get the arguments for a call action
* @param _args general action arguments structure
* @return arguments for a call action
*/
function _parseCallArgs(ActionArgs memory _args) internal pure returns (CallArgs memory) {
require(_args.actionType == ActionType.Call, "A22");
require(_args.secondAddress != address(0), "A23");
return CallArgs({callee: _args.secondAddress, data: _args.data});
}
}
// File: contracts/interfaces/AddressBookInterface.sol
interface AddressBookInterface {
/* Getters */
function getOtokenImpl() external view returns (address);
function getOtokenFactory() external view returns (address);
function getWhitelist() external view returns (address);
function getController() external view returns (address);
function getOracle() external view returns (address);
function getMarginPool() external view returns (address);
function getMarginCalculator() external view returns (address);
function getLiquidationManager() external view returns (address);
function getAddress(bytes32 _id) external view returns (address);
/* Setters */
function setOtokenImpl(address _otokenImpl) external;
function setOtokenFactory(address _factory) external;
function setOracleImpl(address _otokenImpl) external;
function setWhitelist(address _whitelist) external;
function setController(address _controller) external;
function setMarginPool(address _marginPool) external;
function setMarginCalculator(address _calculator) external;
function setLiquidationManager(address _liquidationManager) external;
function setAddress(bytes32 _id, address _newImpl) external;
}
// File: contracts/interfaces/OtokenInterface.sol
interface OtokenInterface {
function addressBook() external view returns (address);
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);
function init(
address _addressBook,
address _underlyingAsset,
address _strikeAsset,
address _collateralAsset,
uint256 _strikePrice,
uint256 _expiry,
bool _isPut
) external;
function getOtokenDetails()
external
view
returns (
address,
address,
address,
uint256,
uint256,
bool
);
function mintOtoken(address account, uint256 amount) external;
function burnOtoken(address account, uint256 amount) external;
}
// File: contracts/interfaces/MarginCalculatorInterface.sol
interface MarginCalculatorInterface {
function addressBook() external view returns (address);
function getExpiredPayoutRate(address _otoken) external view returns (uint256);
function getExcessCollateral(MarginVault.Vault calldata _vault, uint256 _vaultType)
external
view
returns (uint256 netValue, bool isExcess);
function isLiquidatable(
MarginVault.Vault memory _vault,
uint256 _vaultType,
uint256 _vaultLatestUpdate,
uint256 _roundId
)
external
view
returns (
bool,
uint256,
uint256
);
}
// File: contracts/interfaces/OracleInterface.sol
interface OracleInterface {
function isLockingPeriodOver(address _asset, uint256 _expiryTimestamp) external view returns (bool);
function isDisputePeriodOver(address _asset, uint256 _expiryTimestamp) external view returns (bool);
function getExpiryPrice(address _asset, uint256 _expiryTimestamp) external view returns (uint256, bool);
function getDisputer() external view returns (address);
function getPricer(address _asset) external view returns (address);
function getPrice(address _asset) external view returns (uint256);
function getPricerLockingPeriod(address _pricer) external view returns (uint256);
function getPricerDisputePeriod(address _pricer) external view returns (uint256);
function getChainlinkRoundData(address _asset, uint80 _roundId) external view returns (uint256, uint256);
// Non-view function
function setAssetPricer(address _asset, address _pricer) external;
function setLockingPeriod(address _pricer, uint256 _lockingPeriod) external;
function setDisputePeriod(address _pricer, uint256 _disputePeriod) external;
function setExpiryPrice(
address _asset,
uint256 _expiryTimestamp,
uint256 _price
) external;
function disputeExpiryPrice(
address _asset,
uint256 _expiryTimestamp,
uint256 _price
) external;
function setDisputer(address _disputer) external;
}
// File: contracts/interfaces/WhitelistInterface.sol
interface WhitelistInterface {
/* View functions */
function addressBook() external view returns (address);
function isWhitelistedProduct(
address _underlying,
address _strike,
address _collateral,
bool _isPut
) external view returns (bool);
function isWhitelistedCollateral(address _collateral) external view returns (bool);
function isWhitelistedOtoken(address _otoken) external view returns (bool);
function isWhitelistedCallee(address _callee) external view returns (bool);
/* Admin / factory only functions */
function whitelistProduct(
address _underlying,
address _strike,
address _collateral,
bool _isPut
) external;
function blacklistProduct(
address _underlying,
address _strike,
address _collateral,
bool _isPut
) external;
function whitelistCollateral(address _collateral) external;
function blacklistCollateral(address _collateral) external;
function whitelistOtoken(address _otoken) external;
function blacklistOtoken(address _otoken) external;
function whitelistCallee(address _callee) external;
function blacklistCallee(address _callee) external;
}
// File: contracts/interfaces/MarginPoolInterface.sol
interface MarginPoolInterface {
/* Getters */
function addressBook() external view returns (address);
function farmer() external view returns (address);
function getStoredBalance(address _asset) external view returns (uint256);
/* Admin-only functions */
function setFarmer(address _farmer) external;
function farm(
address _asset,
address _receiver,
uint256 _amount
) external;
/* Controller-only functions */
function transferToPool(
address _asset,
address _user,
uint256 _amount
) external;
function transferToUser(
address _asset,
address _user,
uint256 _amount
) external;
function batchTransferToPool(
address[] calldata _asset,
address[] calldata _user,
uint256[] calldata _amount
) external;
function batchTransferToUser(
address[] calldata _asset,
address[] calldata _user,
uint256[] calldata _amount
) external;
}
// File: contracts/interfaces/CalleeInterface.sol
/**
* @dev Contract interface that can be called from Controller as a call action.
*/
interface CalleeInterface {
/**
* Allows users to send this contract arbitrary data.
* @param _sender The msg.sender to Controller
* @param _data Arbitrary data given by the sender
*/
function callFunction(address payable _sender, bytes memory _data) external;
}
// File: contracts/core/Controller.sol
/**
* Controller Error Codes
* C1: sender is not full pauser
* C2: sender is not partial pauser
* C3: callee is not a whitelisted address
* C4: system is partially paused
* C5: system is fully paused
* C6: msg.sender is not authorized to run action
* C7: invalid addressbook address
* C8: invalid owner address
* C9: invalid input
* C10: fullPauser cannot be set to address zero
* C11: partialPauser cannot be set to address zero
* C12: can not run actions for different owners
* C13: can not run actions on different vaults
* C14: invalid final vault state
* C15: can not run actions on inexistent vault
* C16: cannot deposit long otoken from this address
* C17: otoken is not whitelisted to be used as collateral
* C18: otoken used as collateral is already expired
* C19: can not withdraw an expired otoken
* C20: cannot deposit collateral from this address
* C21: asset is not whitelisted to be used as collateral
* C22: can not withdraw collateral from a vault with an expired short otoken
* C23: otoken is not whitelisted to be minted
* C24: can not mint expired otoken
* C25: cannot burn from this address
* C26: can not burn expired otoken
* C27: otoken is not whitelisted to be redeemed
* C28: can not redeem un-expired otoken
* C29: asset prices not finalized yet
* C30: can't settle vault with no otoken
* C31: can not settle vault with un-expired otoken
* C32: can not settle undercollateralized vault
* C33: can not liquidate vault
* C34: can not leave less than collateral dust
* C35: invalid vault id
* C36: cap amount should be greater than zero
* C37: collateral exceed naked margin cap
*/
/**
* @title Controller
* @author Opyn Team
* @notice Contract that controls the Gamma Protocol and the interaction of all sub contracts
*/
contract Controller is Initializable, OwnableUpgradeSafe, ReentrancyGuardUpgradeSafe {
using MarginVault for MarginVault.Vault;
using SafeMath for uint256;
AddressBookInterface public addressbook;
WhitelistInterface public whitelist;
OracleInterface public oracle;
MarginCalculatorInterface public calculator;
MarginPoolInterface public pool;
///@dev scale used in MarginCalculator
uint256 internal constant BASE = 8;
/// @notice address that has permission to partially pause the system, where system functionality is paused
/// except redeem and settleVault
address public partialPauser;
/// @notice address that has permission to fully pause the system, where all system functionality is paused
address public fullPauser;
/// @notice True if all system functionality is paused other than redeem and settle vault
bool public systemPartiallyPaused;
/// @notice True if all system functionality is paused
bool public systemFullyPaused;
/// @notice True if a call action can only be executed to a whitelisted callee
bool public callRestricted;
/// @dev mapping between an owner address and the number of owner address vaults
mapping(address => uint256) internal accountVaultCounter;
/// @dev mapping between an owner address and a specific vault using a vault id
mapping(address => mapping(uint256 => MarginVault.Vault)) internal vaults;
/// @dev mapping between an account owner and their approved or unapproved account operators
mapping(address => mapping(address => bool)) internal operators;
/******************************************************************** V2.0.0 storage upgrade ******************************************************/
/// @dev mapping to map vault by each vault type, naked margin vault should be set to 1, spread/max loss vault should be set to 0
mapping(address => mapping(uint256 => uint256)) internal vaultType;
/// @dev mapping to store the timestamp at which the vault was last updated, will be updated in every action that changes the vault state or when calling sync()
mapping(address => mapping(uint256 => uint256)) internal vaultLatestUpdate;
/// @dev mapping to store cap amount for naked margin vault per options collateral asset (scaled by collateral asset decimals)
mapping(address => uint256) internal nakedCap;
/// @dev mapping to store amount of naked margin vaults in pool
mapping(address => uint256) internal nakedPoolBalance;
/// @notice emits an event when an account operator is updated for a specific account owner
event AccountOperatorUpdated(address indexed accountOwner, address indexed operator, bool isSet);
/// @notice emits an event when a new vault is opened
event VaultOpened(address indexed accountOwner, uint256 vaultId, uint256 indexed vaultType);
/// @notice emits an event when a long oToken is deposited into a vault
event LongOtokenDeposited(
address indexed otoken,
address indexed accountOwner,
address indexed from,
uint256 vaultId,
uint256 amount
);
/// @notice emits an event when a long oToken is withdrawn from a vault
event LongOtokenWithdrawed(
address indexed otoken,
address indexed AccountOwner,
address indexed to,
uint256 vaultId,
uint256 amount
);
/// @notice emits an event when a collateral asset is deposited into a vault
event CollateralAssetDeposited(
address indexed asset,
address indexed accountOwner,
address indexed from,
uint256 vaultId,
uint256 amount
);
/// @notice emits an event when a collateral asset is withdrawn from a vault
event CollateralAssetWithdrawed(
address indexed asset,
address indexed AccountOwner,
address indexed to,
uint256 vaultId,
uint256 amount
);
/// @notice emits an event when a short oToken is minted from a vault
event ShortOtokenMinted(
address indexed otoken,
address indexed AccountOwner,
address indexed to,
uint256 vaultId,
uint256 amount
);
/// @notice emits an event when a short oToken is burned
event ShortOtokenBurned(
address indexed otoken,
address indexed AccountOwner,
address indexed from,
uint256 vaultId,
uint256 amount
);
/// @notice emits an event when an oToken is redeemed
event Redeem(
address indexed otoken,
address indexed redeemer,
address indexed receiver,
address collateralAsset,
uint256 otokenBurned,
uint256 payout
);
/// @notice emits an event when a vault is settled
event VaultSettled(
address indexed accountOwner,
address indexed oTokenAddress,
address to,
uint256 payout,
uint256 vaultId,
uint256 indexed vaultType
);
/// @notice emits an event when a vault is liquidated
event VaultLiquidated(
address indexed liquidator,
address indexed receiver,
address indexed vaultOwner,
uint256 auctionPrice,
uint256 auctionStartingRound,
uint256 collateralPayout,
uint256 debtAmount,
uint256 vaultId
);
/// @notice emits an event when a call action is executed
event CallExecuted(address indexed from, address indexed to, bytes data);
/// @notice emits an event when the fullPauser address changes
event FullPauserUpdated(address indexed oldFullPauser, address indexed newFullPauser);
/// @notice emits an event when the partialPauser address changes
event PartialPauserUpdated(address indexed oldPartialPauser, address indexed newPartialPauser);
/// @notice emits an event when the system partial paused status changes
event SystemPartiallyPaused(bool isPaused);
/// @notice emits an event when the system fully paused status changes
event SystemFullyPaused(bool isPaused);
/// @notice emits an event when the call action restriction changes
event CallRestricted(bool isRestricted);
/// @notice emits an event when a donation transfer executed
event Donated(address indexed donator, address indexed asset, uint256 amount);
/// @notice emits an event when naked cap is updated
event NakedCapUpdated(address indexed collateral, uint256 cap);
/**
* @notice modifier to check if the system is not partially paused, where only redeem and settleVault is allowed
*/
modifier notPartiallyPaused {
_isNotPartiallyPaused();
_;
}
/**
* @notice modifier to check if the system is not fully paused, where no functionality is allowed
*/
modifier notFullyPaused {
_isNotFullyPaused();
_;
}
/**
* @notice modifier to check if sender is the fullPauser address
*/
modifier onlyFullPauser {
require(msg.sender == fullPauser, "C1");
_;
}
/**
* @notice modifier to check if the sender is the partialPauser address
*/
modifier onlyPartialPauser {
require(msg.sender == partialPauser, "C2");
_;
}
/**
* @notice modifier to check if the sender is the account owner or an approved account operator
* @param _sender sender address
* @param _accountOwner account owner address
*/
modifier onlyAuthorized(address _sender, address _accountOwner) {
_isAuthorized(_sender, _accountOwner);
_;
}
/**
* @notice modifier to check if the called address is a whitelisted callee address
* @param _callee called address
*/
modifier onlyWhitelistedCallee(address _callee) {
if (callRestricted) {
require(_isCalleeWhitelisted(_callee), "C3");
}
_;
}
/**
* @dev check if the system is not in a partiallyPaused state
*/
function _isNotPartiallyPaused() internal view {
require(!systemPartiallyPaused, "C4");
}
/**
* @dev check if the system is not in an fullyPaused state
*/
function _isNotFullyPaused() internal view {
require(!systemFullyPaused, "C5");
}
/**
* @dev check if the sender is an authorized operator
* @param _sender msg.sender
* @param _accountOwner owner of a vault
*/
function _isAuthorized(address _sender, address _accountOwner) internal view {
require((_sender == _accountOwner) || (operators[_accountOwner][_sender]), "C6");
}
/**
* @notice initalize the deployed contract
* @param _addressBook addressbook module
* @param _owner account owner address
*/
function initialize(address _addressBook, address _owner) external initializer {
require(_addressBook != address(0), "C7");
require(_owner != address(0), "C8");
__Ownable_init(_owner);
__ReentrancyGuard_init_unchained();
addressbook = AddressBookInterface(_addressBook);
_refreshConfigInternal();
callRestricted = true;
}
/**
* @notice send asset amount to margin pool
* @dev use donate() instead of direct transfer() to store the balance in assetBalance
* @param _asset asset address
* @param _amount amount to donate to pool
*/
function donate(address _asset, uint256 _amount) external {
pool.transferToPool(_asset, msg.sender, _amount);
emit Donated(msg.sender, _asset, _amount);
}
/**
* @notice allows the partialPauser to toggle the systemPartiallyPaused variable and partially pause or partially unpause the system
* @dev can only be called by the partialPauser
* @param _partiallyPaused new boolean value to set systemPartiallyPaused to
*/
function setSystemPartiallyPaused(bool _partiallyPaused) external onlyPartialPauser {
require(systemPartiallyPaused != _partiallyPaused, "C9");
systemPartiallyPaused = _partiallyPaused;
emit SystemPartiallyPaused(systemPartiallyPaused);
}
/**
* @notice allows the fullPauser to toggle the systemFullyPaused variable and fully pause or fully unpause the system
* @dev can only be called by the fullyPauser
* @param _fullyPaused new boolean value to set systemFullyPaused to
*/
function setSystemFullyPaused(bool _fullyPaused) external onlyFullPauser {
require(systemFullyPaused != _fullyPaused, "C9");
systemFullyPaused = _fullyPaused;
emit SystemFullyPaused(systemFullyPaused);
}
/**
* @notice allows the owner to set the fullPauser address
* @dev can only be called by the owner
* @param _fullPauser new fullPauser address
*/
function setFullPauser(address _fullPauser) external onlyOwner {
require(_fullPauser != address(0), "C10");
require(fullPauser != _fullPauser, "C9");
emit FullPauserUpdated(fullPauser, _fullPauser);
fullPauser = _fullPauser;
}
/**
* @notice allows the owner to set the partialPauser address
* @dev can only be called by the owner
* @param _partialPauser new partialPauser address
*/
function setPartialPauser(address _partialPauser) external onlyOwner {
require(_partialPauser != address(0), "C11");
require(partialPauser != _partialPauser, "C9");
emit PartialPauserUpdated(partialPauser, _partialPauser);
partialPauser = _partialPauser;
}
/**
* @notice allows the owner to toggle the restriction on whitelisted call actions and only allow whitelisted
* call addresses or allow any arbitrary call addresses
* @dev can only be called by the owner
* @param _isRestricted new call restriction state
*/
function setCallRestriction(bool _isRestricted) external onlyOwner {
require(callRestricted != _isRestricted, "C9");
callRestricted = _isRestricted;
emit CallRestricted(callRestricted);
}
/**
* @notice allows a user to give or revoke privileges to an operator which can act on their behalf on their vaults
* @dev can only be updated by the vault owner
* @param _operator operator that the sender wants to give privileges to or revoke them from
* @param _isOperator new boolean value that expresses if the sender is giving or revoking privileges for _operator
*/
function setOperator(address _operator, bool _isOperator) external {
require(operators[msg.sender][_operator] != _isOperator, "C9");
operators[msg.sender][_operator] = _isOperator;
emit AccountOperatorUpdated(msg.sender, _operator, _isOperator);
}
/**
* @dev updates the configuration of the controller. can only be called by the owner
*/
function refreshConfiguration() external onlyOwner {
_refreshConfigInternal();
}
/**
* @notice set cap amount for collateral asset used in naked margin
* @dev can only be called by owner
* @param _collateral collateral asset address
* @param _cap cap amount, should be scaled by collateral asset decimals
*/
function setNakedCap(address _collateral, uint256 _cap) external onlyOwner {
require(_cap > 0, "C36");
nakedCap[_collateral] = _cap;
emit NakedCapUpdated(_collateral, _cap);
}
/**
* @notice execute a number of actions on specific vaults
* @dev can only be called when the system is not fully paused
* @param _actions array of actions arguments
*/
function operate(Actions.ActionArgs[] memory _actions) external nonReentrant notFullyPaused {
(bool vaultUpdated, address vaultOwner, uint256 vaultId) = _runActions(_actions);
if (vaultUpdated) {
_verifyFinalState(vaultOwner, vaultId);
vaultLatestUpdate[vaultOwner][vaultId] = now;
}
}
/**
* @notice sync vault latest update timestamp
* @dev anyone can update the latest time the vault was touched by calling this function
* vaultLatestUpdate will sync if the vault is well collateralized
* @param _owner vault owner address
* @param _vaultId vault id
*/
function sync(address _owner, uint256 _vaultId) external nonReentrant notFullyPaused {
_verifyFinalState(_owner, _vaultId);
vaultLatestUpdate[_owner][_vaultId] = now;
}
/**
* @notice check if a specific address is an operator for an owner account
* @param _owner account owner address
* @param _operator account operator address
* @return True if the _operator is an approved operator for the _owner account
*/
function isOperator(address _owner, address _operator) external view returns (bool) {
return operators[_owner][_operator];
}
/**
* @notice returns the current controller configuration
* @return whitelist, the address of the whitelist module
* @return oracle, the address of the oracle module
* @return calculator, the address of the calculator module
* @return pool, the address of the pool module
*/
function getConfiguration()
external
view
returns (
address,
address,
address,
address
)
{
return (address(whitelist), address(oracle), address(calculator), address(pool));
}
/**
* @notice return a vault's proceeds pre or post expiry, the amount of collateral that can be removed from a vault
* @param _owner account owner of the vault
* @param _vaultId vaultId to return balances for
* @return amount of collateral that can be taken out
*/
function getProceed(address _owner, uint256 _vaultId) external view returns (uint256) {
(MarginVault.Vault memory vault, uint256 typeVault, ) = getVaultWithDetails(_owner, _vaultId);
(uint256 netValue, bool isExcess) = calculator.getExcessCollateral(vault, typeVault);
if (!isExcess) return 0;
return netValue;
}
/**
* @notice check if a vault is liquidatable in a specific round id
* @param _owner vault owner address
* @param _vaultId vault id to check
* @param _roundId chainlink round id to check vault status at
* @return isUnderCollat, true if vault is undercollateralized, the price of 1 repaid otoken and the otoken collateral dust amount
*/
function isLiquidatable(
address _owner,
uint256 _vaultId,
uint256 _roundId
)
external
view
returns (
bool,
uint256,
uint256
)
{
(, bool isUnderCollat, uint256 price, uint256 dust) = _isLiquidatable(_owner, _vaultId, _roundId);
return (isUnderCollat, price, dust);
}
/**
* @notice get an oToken's payout/cash value after expiry, in the collateral asset
* @param _otoken oToken address
* @param _amount amount of the oToken to calculate the payout for, always represented in 1e8
* @return amount of collateral to pay out
*/
function getPayout(address _otoken, uint256 _amount) public view returns (uint256) {
return calculator.getExpiredPayoutRate(_otoken).mul(_amount).div(10**BASE);
}
/**
* @dev return if an expired oToken is ready to be settled, only true when price for underlying,
* strike and collateral assets at this specific expiry is available in our Oracle module
* @param _otoken oToken
*/
function isSettlementAllowed(address _otoken) external view returns (bool) {
(address underlying, address strike, address collateral, uint256 expiry) = _getOtokenDetails(_otoken);
return _canSettleAssets(underlying, strike, collateral, expiry);
}
/**
* @dev return if underlying, strike, collateral are all allowed to be settled
* @param _underlying oToken underlying asset
* @param _strike oToken strike asset
* @param _collateral oToken collateral asset
* @param _expiry otoken expiry timestamp
* @return True if the oToken has expired AND all oracle prices at the expiry timestamp have been finalized, False if not
*/
function canSettleAssets(
address _underlying,
address _strike,
address _collateral,
uint256 _expiry
) external view returns (bool) {
return _canSettleAssets(_underlying, _strike, _collateral, _expiry);
}
/**
* @notice get the number of vaults for a specified account owner
* @param _accountOwner account owner address
* @return number of vaults
*/
function getAccountVaultCounter(address _accountOwner) external view returns (uint256) {
return accountVaultCounter[_accountOwner];
}
/**
* @notice check if an oToken has expired
* @param _otoken oToken address
* @return True if the otoken has expired, False if not
*/
function hasExpired(address _otoken) external view returns (bool) {
return now >= OtokenInterface(_otoken).expiryTimestamp();
}
/**
* @notice return a specific vault
* @param _owner account owner
* @param _vaultId vault id of vault to return
* @return Vault struct that corresponds to the _vaultId of _owner
*/
function getVault(address _owner, uint256 _vaultId) external view returns (MarginVault.Vault memory) {
return (vaults[_owner][_vaultId]);
}
/**
* @notice return a specific vault
* @param _owner account owner
* @param _vaultId vault id of vault to return
* @return Vault struct that corresponds to the _vaultId of _owner, vault type and the latest timestamp when the vault was updated
*/
function getVaultWithDetails(address _owner, uint256 _vaultId)
public
view
returns (
MarginVault.Vault memory,
uint256,
uint256
)
{
return (vaults[_owner][_vaultId], vaultType[_owner][_vaultId], vaultLatestUpdate[_owner][_vaultId]);
}
/**
* @notice get cap amount for collateral asset
* @param _asset collateral asset address
* @return cap amount
*/
function getNakedCap(address _asset) external view returns (uint256) {
return nakedCap[_asset];
}
/**
* @notice get amount of collateral deposited in all naked margin vaults
* @param _asset collateral asset address
* @return naked pool balance
*/
function getNakedPoolBalance(address _asset) external view returns (uint256) {
return nakedPoolBalance[_asset];
}
/**
* @notice execute a variety of actions
* @dev for each action in the action array, execute the corresponding action, only one vault can be modified
* for all actions except SettleVault, Redeem, and Call
* @param _actions array of type Actions.ActionArgs[], which expresses which actions the user wants to execute
* @return vaultUpdated, indicates if a vault has changed
* @return owner, the vault owner if a vault has changed
* @return vaultId, the vault Id if a vault has changed
*/
function _runActions(Actions.ActionArgs[] memory _actions)
internal
returns (
bool,
address,
uint256
)
{
address vaultOwner;
uint256 vaultId;
bool vaultUpdated;
for (uint256 i = 0; i < _actions.length; i++) {
Actions.ActionArgs memory action = _actions[i];
Actions.ActionType actionType = action.actionType;
// actions except Settle, Redeem, Liquidate and Call are "Vault-updating actinos"
// only allow update 1 vault in each operate call
if (
(actionType != Actions.ActionType.SettleVault) &&
(actionType != Actions.ActionType.Redeem) &&
(actionType != Actions.ActionType.Liquidate) &&
(actionType != Actions.ActionType.Call)
) {
// check if this action is manipulating the same vault as all other actions, if a vault has already been updated
if (vaultUpdated) {
require(vaultOwner == action.owner, "C12");
require(vaultId == action.vaultId, "C13");
}
vaultUpdated = true;
vaultId = action.vaultId;
vaultOwner = action.owner;
}
if (actionType == Actions.ActionType.OpenVault) {
_openVault(Actions._parseOpenVaultArgs(action));
} else if (actionType == Actions.ActionType.DepositLongOption) {
_depositLong(Actions._parseDepositArgs(action));
} else if (actionType == Actions.ActionType.WithdrawLongOption) {
_withdrawLong(Actions._parseWithdrawArgs(action));
} else if (actionType == Actions.ActionType.DepositCollateral) {
_depositCollateral(Actions._parseDepositArgs(action));
} else if (actionType == Actions.ActionType.WithdrawCollateral) {
_withdrawCollateral(Actions._parseWithdrawArgs(action));
} else if (actionType == Actions.ActionType.MintShortOption) {
_mintOtoken(Actions._parseMintArgs(action));
} else if (actionType == Actions.ActionType.BurnShortOption) {
_burnOtoken(Actions._parseBurnArgs(action));
} else if (actionType == Actions.ActionType.Redeem) {
_redeem(Actions._parseRedeemArgs(action));
} else if (actionType == Actions.ActionType.SettleVault) {
_settleVault(Actions._parseSettleVaultArgs(action));
} else if (actionType == Actions.ActionType.Liquidate) {
_liquidate(Actions._parseLiquidateArgs(action));
} else if (actionType == Actions.ActionType.Call) {
_call(Actions._parseCallArgs(action));
}
}
return (vaultUpdated, vaultOwner, vaultId);
}
/**
* @notice verify the vault final state after executing all actions
* @param _owner account owner address
* @param _vaultId vault id of the final vault
*/
function _verifyFinalState(address _owner, uint256 _vaultId) internal view {
(MarginVault.Vault memory vault, uint256 typeVault, ) = getVaultWithDetails(_owner, _vaultId);
(, bool isValidVault) = calculator.getExcessCollateral(vault, typeVault);
require(isValidVault, "C14");
}
/**
* @notice open a new vault inside an account
* @dev only the account owner or operator can open a vault, cannot be called when system is partiallyPaused or fullyPaused
* @param _args OpenVaultArgs structure
*/
function _openVault(Actions.OpenVaultArgs memory _args)
internal
notPartiallyPaused
onlyAuthorized(msg.sender, _args.owner)
{
uint256 vaultId = accountVaultCounter[_args.owner].add(1);
require(_args.vaultId == vaultId, "C15");
// store new vault
accountVaultCounter[_args.owner] = vaultId;
vaultType[_args.owner][vaultId] = _args.vaultType;
emit VaultOpened(_args.owner, vaultId, _args.vaultType);
}
/**
* @notice deposit a long oToken into a vault
* @dev only the account owner or operator can deposit a long oToken, cannot be called when system is partiallyPaused or fullyPaused
* @param _args DepositArgs structure
*/
function _depositLong(Actions.DepositArgs memory _args)
internal
notPartiallyPaused
onlyAuthorized(msg.sender, _args.owner)
{
require(_checkVaultId(_args.owner, _args.vaultId), "C35");
// only allow vault owner or vault operator to deposit long otoken
require((_args.from == msg.sender) || (_args.from == _args.owner), "C16");
require(whitelist.isWhitelistedOtoken(_args.asset), "C17");
OtokenInterface otoken = OtokenInterface(_args.asset);
require(now < otoken.expiryTimestamp(), "C18");
vaults[_args.owner][_args.vaultId].addLong(_args.asset, _args.amount, _args.index);
pool.transferToPool(_args.asset, _args.from, _args.amount);
emit LongOtokenDeposited(_args.asset, _args.owner, _args.from, _args.vaultId, _args.amount);
}
/**
* @notice withdraw a long oToken from a vault
* @dev only the account owner or operator can withdraw a long oToken, cannot be called when system is partiallyPaused or fullyPaused
* @param _args WithdrawArgs structure
*/
function _withdrawLong(Actions.WithdrawArgs memory _args)
internal
notPartiallyPaused
onlyAuthorized(msg.sender, _args.owner)
{
require(_checkVaultId(_args.owner, _args.vaultId), "C35");
OtokenInterface otoken = OtokenInterface(_args.asset);
require(now < otoken.expiryTimestamp(), "C19");
vaults[_args.owner][_args.vaultId].removeLong(_args.asset, _args.amount, _args.index);
pool.transferToUser(_args.asset, _args.to, _args.amount);
emit LongOtokenWithdrawed(_args.asset, _args.owner, _args.to, _args.vaultId, _args.amount);
}
/**
* @notice deposit a collateral asset into a vault
* @dev only the account owner or operator can deposit collateral, cannot be called when system is partiallyPaused or fullyPaused
* @param _args DepositArgs structure
*/
function _depositCollateral(Actions.DepositArgs memory _args)
internal
notPartiallyPaused
onlyAuthorized(msg.sender, _args.owner)
{
require(_checkVaultId(_args.owner, _args.vaultId), "C35");
// only allow vault owner or vault operator to deposit collateral
require((_args.from == msg.sender) || (_args.from == _args.owner), "C20");
require(whitelist.isWhitelistedCollateral(_args.asset), "C21");
(, uint256 typeVault, ) = getVaultWithDetails(_args.owner, _args.vaultId);
if (typeVault == 1) {
nakedPoolBalance[_args.asset] = nakedPoolBalance[_args.asset].add(_args.amount);
require(nakedPoolBalance[_args.asset] <= nakedCap[_args.asset], "C37");
}
vaults[_args.owner][_args.vaultId].addCollateral(_args.asset, _args.amount, _args.index);
pool.transferToPool(_args.asset, _args.from, _args.amount);
emit CollateralAssetDeposited(_args.asset, _args.owner, _args.from, _args.vaultId, _args.amount);
}
/**
* @notice withdraw a collateral asset from a vault
* @dev only the account owner or operator can withdraw collateral, cannot be called when system is partiallyPaused or fullyPaused
* @param _args WithdrawArgs structure
*/
function _withdrawCollateral(Actions.WithdrawArgs memory _args)
internal
notPartiallyPaused
onlyAuthorized(msg.sender, _args.owner)
{
require(_checkVaultId(_args.owner, _args.vaultId), "C35");
(MarginVault.Vault memory vault, uint256 typeVault, ) = getVaultWithDetails(_args.owner, _args.vaultId);
if (_isNotEmpty(vault.shortOtokens)) {
OtokenInterface otoken = OtokenInterface(vault.shortOtokens[0]);
require(now < otoken.expiryTimestamp(), "C22");
}
if (typeVault == 1) {
nakedPoolBalance[_args.asset] = nakedPoolBalance[_args.asset].sub(_args.amount);
}
vaults[_args.owner][_args.vaultId].removeCollateral(_args.asset, _args.amount, _args.index);
pool.transferToUser(_args.asset, _args.to, _args.amount);
emit CollateralAssetWithdrawed(_args.asset, _args.owner, _args.to, _args.vaultId, _args.amount);
}
/**
* @notice mint short oTokens from a vault which creates an obligation that is recorded in the vault
* @dev only the account owner or operator can mint an oToken, cannot be called when system is partiallyPaused or fullyPaused
* @param _args MintArgs structure
*/
function _mintOtoken(Actions.MintArgs memory _args)
internal
notPartiallyPaused
onlyAuthorized(msg.sender, _args.owner)
{
require(_checkVaultId(_args.owner, _args.vaultId), "C35");
require(whitelist.isWhitelistedOtoken(_args.otoken), "C23");
OtokenInterface otoken = OtokenInterface(_args.otoken);
require(now < otoken.expiryTimestamp(), "C24");
vaults[_args.owner][_args.vaultId].addShort(_args.otoken, _args.amount, _args.index);
otoken.mintOtoken(_args.to, _args.amount);
emit ShortOtokenMinted(_args.otoken, _args.owner, _args.to, _args.vaultId, _args.amount);
}
/**
* @notice burn oTokens to reduce or remove the minted oToken obligation recorded in a vault
* @dev only the account owner or operator can burn an oToken, cannot be called when system is partiallyPaused or fullyPaused
* @param _args MintArgs structure
*/
function _burnOtoken(Actions.BurnArgs memory _args)
internal
notPartiallyPaused
onlyAuthorized(msg.sender, _args.owner)
{
// check that vault id is valid for this vault owner
require(_checkVaultId(_args.owner, _args.vaultId), "C35");
// only allow vault owner or vault operator to burn otoken
require((_args.from == msg.sender) || (_args.from == _args.owner), "C25");
OtokenInterface otoken = OtokenInterface(_args.otoken);
// do not allow burning expired otoken
require(now < otoken.expiryTimestamp(), "C26");
// remove otoken from vault
vaults[_args.owner][_args.vaultId].removeShort(_args.otoken, _args.amount, _args.index);
// burn otoken
otoken.burnOtoken(_args.from, _args.amount);
emit ShortOtokenBurned(_args.otoken, _args.owner, _args.from, _args.vaultId, _args.amount);
}
/**
* @notice redeem an oToken after expiry, receiving the payout of the oToken in the collateral asset
* @dev cannot be called when system is fullyPaused
* @param _args RedeemArgs structure
*/
function _redeem(Actions.RedeemArgs memory _args) internal {
OtokenInterface otoken = OtokenInterface(_args.otoken);
// check that otoken to redeem is whitelisted
require(whitelist.isWhitelistedOtoken(_args.otoken), "C27");
(address collateral, address underlying, address strike, uint256 expiry) = _getOtokenDetails(address(otoken));
// only allow redeeming expired otoken
require(now >= expiry, "C28");
require(_canSettleAssets(underlying, strike, collateral, expiry), "C29");
uint256 payout = getPayout(_args.otoken, _args.amount);
otoken.burnOtoken(msg.sender, _args.amount);
pool.transferToUser(collateral, _args.receiver, payout);
emit Redeem(_args.otoken, msg.sender, _args.receiver, collateral, _args.amount, payout);
}
/**
* @notice settle a vault after expiry, removing the net proceeds/collateral after both long and short oToken payouts have settled
* @dev deletes a vault of vaultId after net proceeds/collateral is removed, cannot be called when system is fullyPaused
* @param _args SettleVaultArgs structure
*/
function _settleVault(Actions.SettleVaultArgs memory _args) internal onlyAuthorized(msg.sender, _args.owner) {
require(_checkVaultId(_args.owner, _args.vaultId), "C35");
(MarginVault.Vault memory vault, uint256 typeVault, ) = getVaultWithDetails(_args.owner, _args.vaultId);
OtokenInterface otoken;
// new scope to avoid stack too deep error
// check if there is short or long otoken in vault
// do not allow settling vault that have no short or long otoken
// if there is a long otoken, burn it
// store otoken address outside of this scope
{
bool hasShort = _isNotEmpty(vault.shortOtokens);
bool hasLong = _isNotEmpty(vault.longOtokens);
require(hasShort || hasLong, "C30");
otoken = hasShort ? OtokenInterface(vault.shortOtokens[0]) : OtokenInterface(vault.longOtokens[0]);
if (hasLong) {
OtokenInterface longOtoken = OtokenInterface(vault.longOtokens[0]);
longOtoken.burnOtoken(address(pool), vault.longAmounts[0]);
}
}
(address collateral, address underlying, address strike, uint256 expiry) = _getOtokenDetails(address(otoken));
// do not allow settling vault with un-expired otoken
require(now >= expiry, "C31");
require(_canSettleAssets(underlying, strike, collateral, expiry), "C29");
(uint256 payout, bool isValidVault) = calculator.getExcessCollateral(vault, typeVault);
// require that vault is valid (has excess collateral) before settling
// to avoid allowing settling undercollateralized naked margin vault
require(isValidVault, "C32");
delete vaults[_args.owner][_args.vaultId];
if (typeVault == 1) {
nakedPoolBalance[collateral] = nakedPoolBalance[collateral].sub(payout);
}
pool.transferToUser(collateral, _args.to, payout);
uint256 vaultId = _args.vaultId;
address payoutRecipient = _args.to;
emit VaultSettled(_args.owner, address(otoken), payoutRecipient, payout, vaultId, typeVault);
}
/**
* @notice liquidate naked margin vault
* @dev can liquidate different vaults id in the same operate() call
* @param _args liquidation action arguments struct
*/
function _liquidate(Actions.LiquidateArgs memory _args) internal notPartiallyPaused {
require(_checkVaultId(_args.owner, _args.vaultId), "C35");
// check if vault is undercollateralized
// the price is the amount of collateral asset to pay per 1 repaid debt(otoken)
// collateralDust is the minimum amount of collateral that can be left in the vault when a partial liquidation occurs
(MarginVault.Vault memory vault, bool isUnderCollat, uint256 price, uint256 collateralDust) = _isLiquidatable(
_args.owner,
_args.vaultId,
_args.roundId
);
require(isUnderCollat, "C33");
// amount of collateral to offer to liquidator
uint256 collateralToSell = _args.amount.mul(price).div(1e8);
// if vault is partially liquidated (amount of short otoken is still greater than zero)
// make sure remaining collateral amount is greater than dust amount
if (vault.shortAmounts[0].sub(_args.amount) > 0) {
require(vault.collateralAmounts[0].sub(collateralToSell) >= collateralDust, "C34");
}
// burn short otoken from liquidator address, index of short otoken hardcoded at 0
// this should always work, if vault have no short otoken, it will not reach this step
OtokenInterface(vault.shortOtokens[0]).burnOtoken(msg.sender, _args.amount);
// decrease amount of collateral in liquidated vault, index of collateral to decrease is hardcoded at 0
vaults[_args.owner][_args.vaultId].removeCollateral(vault.collateralAssets[0], collateralToSell, 0);
// decrease amount of short otoken in liquidated vault, index of short otoken to decrease is hardcoded at 0
vaults[_args.owner][_args.vaultId].removeShort(vault.shortOtokens[0], _args.amount, 0);
// decrease internal naked margin collateral amount
nakedPoolBalance[vault.collateralAssets[0]] = nakedPoolBalance[vault.collateralAssets[0]].sub(collateralToSell);
pool.transferToUser(vault.collateralAssets[0], _args.receiver, collateralToSell);
emit VaultLiquidated(
msg.sender,
_args.receiver,
_args.owner,
price,
_args.roundId,
collateralToSell,
_args.amount,
_args.vaultId
);
}
/**
* @notice execute arbitrary calls
* @dev cannot be called when system is partiallyPaused or fullyPaused
* @param _args Call action
*/
function _call(Actions.CallArgs memory _args) internal notPartiallyPaused onlyWhitelistedCallee(_args.callee) {
CalleeInterface(_args.callee).callFunction(msg.sender, _args.data);
emit CallExecuted(msg.sender, _args.callee, _args.data);
}
/**
* @notice check if a vault id is valid for a given account owner address
* @param _accountOwner account owner address
* @param _vaultId vault id to check
* @return True if the _vaultId is valid, False if not
*/
function _checkVaultId(address _accountOwner, uint256 _vaultId) internal view returns (bool) {
return ((_vaultId > 0) && (_vaultId <= accountVaultCounter[_accountOwner]));
}
function _isNotEmpty(address[] memory _array) internal pure returns (bool) {
return (_array.length > 0) && (_array[0] != address(0));
}
/**
* @notice return if a callee address is whitelisted or not
* @param _callee callee address
* @return True if callee address is whitelisted, False if not
*/
function _isCalleeWhitelisted(address _callee) internal view returns (bool) {
return whitelist.isWhitelistedCallee(_callee);
}
/**
* @notice check if a vault is liquidatable in a specific round id
* @param _owner vault owner address
* @param _vaultId vault id to check
* @param _roundId chainlink round id to check vault status at
* @return vault struct, isLiquidatable, true if vault is undercollateralized, the price of 1 repaid otoken and the otoken collateral dust amount
*/
function _isLiquidatable(
address _owner,
uint256 _vaultId,
uint256 _roundId
)
internal
view
returns (
MarginVault.Vault memory,
bool,
uint256,
uint256
)
{
(MarginVault.Vault memory vault, uint256 typeVault, uint256 latestUpdateTimestamp) = getVaultWithDetails(
_owner,
_vaultId
);
(bool isUnderCollat, uint256 price, uint256 collateralDust) = calculator.isLiquidatable(
vault,
typeVault,
latestUpdateTimestamp,
_roundId
);
return (vault, isUnderCollat, price, collateralDust);
}
/**
* @dev get otoken detail, from both otoken versions
*/
function _getOtokenDetails(address _otoken)
internal
view
returns (
address,
address,
address,
uint256
)
{
OtokenInterface otoken = OtokenInterface(_otoken);
try otoken.getOtokenDetails() returns (
address collateral,
address underlying,
address strike,
uint256,
uint256 expiry,
bool
) {
return (collateral, underlying, strike, expiry);
} catch {
return (otoken.collateralAsset(), otoken.underlyingAsset(), otoken.strikeAsset(), otoken.expiryTimestamp());
}
}
/**
* @dev return if an expired oToken is ready to be settled, only true when price for underlying,
* strike and collateral assets at this specific expiry is available in our Oracle module
* @return True if the oToken has expired AND all oracle prices at the expiry timestamp have been finalized, False if not
*/
function _canSettleAssets(
address _underlying,
address _strike,
address _collateral,
uint256 _expiry
) internal view returns (bool) {
return
oracle.isDisputePeriodOver(_underlying, _expiry) &&
oracle.isDisputePeriodOver(_strike, _expiry) &&
oracle.isDisputePeriodOver(_collateral, _expiry);
}
/**
* @dev updates the internal configuration of the controller
*/
function _refreshConfigInternal() internal {
whitelist = WhitelistInterface(addressbook.getWhitelist());
oracle = OracleInterface(addressbook.getOracle());
calculator = MarginCalculatorInterface(addressbook.getMarginCalculator());
pool = MarginPoolInterface(addressbook.getMarginPool());
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment