pragma solidity 0.5.16;
interface MassetStructs {
/** @dev Stores high level basket info */
struct Basket {
/** @dev Array of Bassets currently active */
Basset[] bassets;
/** @dev Max number of bAssets that can be present in any Basket */
uint8 maxBassets;
/** @dev Some bAsset is undergoing re-collateralisation */
bool undergoingRecol;
/**
* @dev In the event that we do not raise enough funds from the auctioning of a failed Basset,
* The Basket is deemed as failed, and is undercollateralised to a certain degree.
* The collateralisation ratio is used to calc Masset burn rate.
*/
bool failed;
uint256 collateralisationRatio;
}
/** @dev Stores bAsset info. The struct takes 5 storage slots per Basset */
struct Basset {
/** @dev Address of the bAsset */
address addr;
/** @dev Status of the basset, */
BassetStatus status; // takes uint8 datatype (1 byte) in storage
/** @dev An ERC20 can charge transfer fee, for example USDT, DGX tokens. */
bool isTransferFeeCharged; // takes a byte in storage
/**
* @dev 1 Basset * ratio / ratioScale == x Masset (relative value)
* If ratio == 10e8 then 1 bAsset = 10 mAssets
* A ratio is divised as 10^(18-tokenDecimals) * measurementMultiple(relative value of 1 base unit)
*/
uint256 ratio;
/** @dev Target weights of the Basset (100% == 1e18) */
uint256 maxWeight;
/** @dev Amount of the Basset that is held in Collateral */
uint256 vaultBalance;
}
/** @dev Status of the Basset - has it broken its peg? */
enum BassetStatus {
Default,
Normal,
BrokenBelowPeg,
BrokenAbovePeg,
Blacklisted,
Liquidating,
Liquidated,
Failed
}
/** @dev Internal details on Basset */
struct BassetDetails {
Basset bAsset;
address integrator;
uint8 index;
}
/** @dev All details needed to Forge with multiple bAssets */
struct ForgePropsMulti {
bool isValid; // Flag to signify that forge bAssets have passed validity check
Basset[] bAssets;
address[] integrators;
uint8[] indexes;
}
/** @dev All details needed for proportionate Redemption */
struct RedeemPropsMulti {
uint256 colRatio;
Basset[] bAssets;
address[] integrators;
uint8[] indexes;
}
}
contract IMasset is MassetStructs {
/** @dev Calc interest */
function collectInterest() external returns (uint256 massetMinted, uint256 newTotalSupply);
/** @dev Minting */
function mint(address _basset, uint256 _bassetQuantity)
external returns (uint256 massetMinted);
function mintTo(address _basset, uint256 _bassetQuantity, address _recipient)
external returns (uint256 massetMinted);
function mintMulti(address[] calldata _bAssets, uint256[] calldata _bassetQuantity, address _recipient)
external returns (uint256 massetMinted);
/** @dev Swapping */
function swap( address _input, address _output, uint256 _quantity, address _recipient)
external returns (uint256 output);
function getSwapOutput( address _input, address _output, uint256 _quantity)
external view returns (bool, string memory, uint256 output);
/** @dev Redeeming */
function redeem(address _basset, uint256 _bassetQuantity)
external returns (uint256 massetRedeemed);
function redeemTo(address _basset, uint256 _bassetQuantity, address _recipient)
external returns (uint256 massetRedeemed);
function redeemMulti(address[] calldata _bAssets, uint256[] calldata _bassetQuantities, address _recipient)
external returns (uint256 massetRedeemed);
function redeemMasset(uint256 _mAssetQuantity, address _recipient) external;
/** @dev Setters for the Manager or Gov to update module info */
function upgradeForgeValidator(address _newForgeValidator) external;
/** @dev Setters for Gov to set system params */
function setSwapFee(uint256 _swapFee) external;
/** @dev Getters */
function getBasketManager() external view returns(address);
function forgeValidator() external view returns (address);
function totalSupply() external view returns (uint256);
function swapFee() external view returns (uint256);
}
interface ISavingsContract {
/** @dev Manager privs */
function depositInterest(uint256 _amount) external;
/** @dev Saver privs */
function depositSavings(uint256 _amount) external returns (uint256 creditsIssued);
function redeem(uint256 _amount) external returns (uint256 massetReturned);
/** @dev Getters */
function exchangeRate() external view returns (uint256);
function creditBalances(address) external view returns (uint256);
}
interface ISavingsManager {
/** @dev Admin privs */
function withdrawUnallocatedInterest(address _mAsset, address _recipient) external;
/** @dev Liquidator */
function depositLiquidation(address _mAsset, uint256 _liquidation) external;
/** @dev Public privs */
function collectAndDistributeInterest(address _mAsset) external;
}
contract ModuleKeys {
// Governance
// ===========
// keccak256("Governance");
bytes32 internal constant KEY_GOVERNANCE = 0x9409903de1e6fd852dfc61c9dacb48196c48535b60e25abf92acc92dd689078d;
//keccak256("Staking");
bytes32 internal constant KEY_STAKING = 0x1df41cd916959d1163dc8f0671a666ea8a3e434c13e40faef527133b5d167034;
//keccak256("ProxyAdmin");
bytes32 internal constant KEY_PROXY_ADMIN = 0x96ed0203eb7e975a4cbcaa23951943fa35c5d8288117d50c12b3d48b0fab48d1;
// mStable
// =======
// keccak256("OracleHub");
bytes32 internal constant KEY_ORACLE_HUB = 0x8ae3a082c61a7379e2280f3356a5131507d9829d222d853bfa7c9fe1200dd040;
// keccak256("Manager");
bytes32 internal constant KEY_MANAGER = 0x6d439300980e333f0256d64be2c9f67e86f4493ce25f82498d6db7f4be3d9e6f;
//keccak256("Recollateraliser");
bytes32 internal constant KEY_RECOLLATERALISER = 0x39e3ed1fc335ce346a8cbe3e64dd525cf22b37f1e2104a755e761c3c1eb4734f;
//keccak256("MetaToken");
bytes32 internal constant KEY_META_TOKEN = 0xea7469b14936af748ee93c53b2fe510b9928edbdccac3963321efca7eb1a57a2;
// keccak256("SavingsManager");
bytes32 internal constant KEY_SAVINGS_MANAGER = 0x12fe936c77a1e196473c4314f3bed8eeac1d757b319abb85bdda70df35511bf1;
// keccak256("Liquidator");
bytes32 internal constant KEY_LIQUIDATOR = 0x1e9cb14d7560734a61fa5ff9273953e971ff3cd9283c03d8346e3264617933d4;
}
interface INexus {
function governor() external view returns (address);
function getModule(bytes32 key) external view returns (address);
function proposeModule(bytes32 _key, address _addr) external;
function cancelProposedModule(bytes32 _key) external;
function acceptProposedModule(bytes32 _key) external;
function acceptProposedModules(bytes32[] calldata _keys) external;
function requestLockModule(bytes32 _key) external;
function cancelLockModule(bytes32 _key) external;
function lockModule(bytes32 _key) external;
}
contract Module is ModuleKeys {
INexus public nexus;
/**
* @dev Initialises the Module by setting publisher addresses,
* and reading all available system module information
*/
constructor(address _nexus) internal {
require(_nexus != address(0), "Nexus is zero address");
nexus = INexus(_nexus);
}
/**
* @dev Modifier to allow function calls only from the Governor.
*/
modifier onlyGovernor() {
require(msg.sender == _governor(), "Only governor can execute");
_;
}
/**
* @dev Modifier to allow function calls only from the Governance.
* Governance is either Governor address or Governance address.
*/
modifier onlyGovernance() {
require(
msg.sender == _governor() || msg.sender == _governance(),
"Only governance can execute"
);
_;
}
/**
* @dev Modifier to allow function calls only from the ProxyAdmin.
*/
modifier onlyProxyAdmin() {
require(
msg.sender == _proxyAdmin(), "Only ProxyAdmin can execute"
);
_;
}
/**
* @dev Modifier to allow function calls only from the Manager.
*/
modifier onlyManager() {
require(msg.sender == _manager(), "Only manager can execute");
_;
}
/**
* @dev Returns Governor address from the Nexus
* @return Address of Governor Contract
*/
function _governor() internal view returns (address) {
return nexus.governor();
}
/**
* @dev Returns Governance Module address from the Nexus
* @return Address of the Governance (Phase 2)
*/
function _governance() internal view returns (address) {
return nexus.getModule(KEY_GOVERNANCE);
}
/**
* @dev Return Staking Module address from the Nexus
* @return Address of the Staking Module contract
*/
function _staking() internal view returns (address) {
return nexus.getModule(KEY_STAKING);
}
/**
* @dev Return ProxyAdmin Module address from the Nexus
* @return Address of the ProxyAdmin Module contract
*/
function _proxyAdmin() internal view returns (address) {
return nexus.getModule(KEY_PROXY_ADMIN);
}
/**
* @dev Return MetaToken Module address from the Nexus
* @return Address of the MetaToken Module contract
*/
function _metaToken() internal view returns (address) {
return nexus.getModule(KEY_META_TOKEN);
}
/**
* @dev Return OracleHub Module address from the Nexus
* @return Address of the OracleHub Module contract
*/
function _oracleHub() internal view returns (address) {
return nexus.getModule(KEY_ORACLE_HUB);
}
/**
* @dev Return Manager Module address from the Nexus
* @return Address of the Manager Module contract
*/
function _manager() internal view returns (address) {
return nexus.getModule(KEY_MANAGER);
}
/**
* @dev Return SavingsManager Module address from the Nexus
* @return Address of the SavingsManager Module contract
*/
function _savingsManager() internal view returns (address) {
return nexus.getModule(KEY_SAVINGS_MANAGER);
}
/**
* @dev Return Recollateraliser Module address from the Nexus
* @return Address of the Recollateraliser Module contract (Phase 2)
*/
function _recollateraliser() internal view returns (address) {
return nexus.getModule(KEY_RECOLLATERALISER);
}
/**
* @dev Return Recollateraliser Module address from the Nexus
* @return Address of the Recollateraliser Module contract (Phase 2)
*/
function _liquidator() internal view returns (address) {
return nexus.getModule(KEY_LIQUIDATOR);
}
}
contract PausableModule is Module {
/**
* @dev Emitted when the pause is triggered by Governor
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by Governor
*/
event Unpaused(address account);
bool private _paused = false;
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*/
modifier whenNotPaused() {
require(!_paused, "Pausable: paused");
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*/
modifier whenPaused() {
require(_paused, "Pausable: not paused");
_;
}
/**
* @dev Initializes the contract in unpaused state.
* Hooks into the Module to give the Governor ability to pause
* @param _nexus Nexus contract address
*/
constructor (address _nexus) internal Module(_nexus) {
_paused = false;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
* @return Returns `true` when paused, otherwise `false`
*/
function paused() external view returns (bool) {
return _paused;
}
/**
* @dev Called by the Governor to pause, triggers stopped state.
*/
function pause() external onlyGovernor whenNotPaused {
_paused = true;
emit Paused(msg.sender);
}
/**
* @dev Called by Governor to unpause, returns to normal state.
*/
function unpause() external onlyGovernor whenPaused {
_paused = false;
emit Unpaused(msg.sender);
}
}
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);
}
/**
* @dev Interface of the ERC20 standard as defined in the EIP. Does not include
* the optional functions; to access them see {ERC20Detailed}.
*/
/**
* @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.
*
* _Available since v2.4.0._
*/
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.
*
* _Available since v2.4.0._
*/
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.
*
* _Available since v2.4.0._
*/
function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b != 0, errorMessage);
return a % b;
}
}
/**
* @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) {
// According to EIP-1052, 0x0 is the value returned for not-yet created accounts
// and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
// for accounts without code, i.e. `keccak256('')`
bytes32 codehash;
bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
// solhint-disable-next-line no-inline-assembly
assembly { codehash := extcodehash(account) }
return (codehash != accountHash && codehash != 0x0);
}
/**
* @dev Converts an `address` into `address payable`. Note that this is
* simply a type cast: the actual underlying value is not changed.
*
* _Available since v2.4.0._
*/
function toPayable(address account) internal pure returns (address payable) {
return address(uint160(account));
}
/**
* @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].
*
* _Available since v2.4.0._
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
// solhint-disable-next-line avoid-call-value
(bool success, ) = recipient.call.value(amount)("");
require(success, "Address: unable to send value, recipient may have reverted");
}
}
library SafeERC20 {
using SafeMath for uint256;
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));
}
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'
// solhint-disable-next-line max-line-length
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).add(value);
callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero");
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.
// A Solidity high level call has three parts:
// 1. The target address is checked to verify it contains contract code
// 2. The call itself is made, and success asserted
// 3. The return value is decoded, which in turn checks the size of the returned data.
// solhint-disable-next-line max-line-length
require(address(token).isContract(), "SafeERC20: call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = address(token).call(data);
require(success, "SafeERC20: low-level call failed");
if (returndata.length > 0) { // Return data is optional
// solhint-disable-next-line max-line-length
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
library StableMath {
using SafeMath for uint256;
/**
* @dev Scaling unit for use in specific calculations,
* where 1 * 10**18, or 1e18 represents a unit '1'
*/
uint256 private constant FULL_SCALE = 1e18;
/**
* @notice Token Ratios are used when converting between units of bAsset, mAsset and MTA
* Reasoning: Takes into account token decimals, and difference in base unit (i.e. grams to Troy oz for gold)
* @dev bAsset ratio unit for use in exact calculations,
* where (1 bAsset unit * bAsset.ratio) / ratioScale == x mAsset unit
*/
uint256 private constant RATIO_SCALE = 1e8;
/**
* @dev Provides an interface to the scaling unit
* @return Scaling unit (1e18 or 1 * 10**18)
*/
function getFullScale() internal pure returns (uint256) {
return FULL_SCALE;
}
/**
* @dev Provides an interface to the ratio unit
* @return Ratio scale unit (1e8 or 1 * 10**8)
*/
function getRatioScale() internal pure returns (uint256) {
return RATIO_SCALE;
}
/**
* @dev Scales a given integer to the power of the full scale.
* @param x Simple uint256 to scale
* @return Scaled value a to an exact number
*/
function scaleInteger(uint256 x)
internal
pure
returns (uint256)
{
return x.mul(FULL_SCALE);
}
/***************************************
PRECISE ARITHMETIC
****************************************/
/**
* @dev Multiplies two precise units, and then truncates by the full scale
* @param x Left hand input to multiplication
* @param y Right hand input to multiplication
* @return Result after multiplying the two inputs and then dividing by the shared
* scale unit
*/
function mulTruncate(uint256 x, uint256 y)
internal
pure
returns (uint256)
{
return mulTruncateScale(x, y, FULL_SCALE);
}
/**
* @dev Multiplies two precise units, and then truncates by the given scale. For example,
* when calculating 90% of 10e18, (10e18 * 9e17) / 1e18 = (9e36) / 1e18 = 9e18
* @param x Left hand input to multiplication
* @param y Right hand input to multiplication
* @param scale Scale unit
* @return Result after multiplying the two inputs and then dividing by the shared
* scale unit
*/
function mulTruncateScale(uint256 x, uint256 y, uint256 scale)
internal
pure
returns (uint256)
{
// e.g. assume scale = fullScale
// z = 10e18 * 9e17 = 9e36
uint256 z = x.mul(y);
// return 9e38 / 1e18 = 9e18
return z.div(scale);
}
/**
* @dev Multiplies two precise units, and then truncates by the full scale, rounding up the result
* @param x Left hand input to multiplication
* @param y Right hand input to multiplication
* @return Result after multiplying the two inputs and then dividing by the shared
* scale unit, rounded up to the closest base unit.
*/
function mulTruncateCeil(uint256 x, uint256 y)
internal
pure
returns (uint256)
{
// e.g. 8e17 * 17268172638 = 138145381104e17
uint256 scaled = x.mul(y);
// e.g. 138145381104e17 + 9.99...e17 = 138145381113.99...e17
uint256 ceil = scaled.add(FULL_SCALE.sub(1));
// e.g. 13814538111.399...e18 / 1e18 = 13814538111
return ceil.div(FULL_SCALE);
}
/**
* @dev Precisely divides two units, by first scaling the left hand operand. Useful
* for finding percentage weightings, i.e. 8e18/10e18 = 80% (or 8e17)
* @param x Left hand input to division
* @param y Right hand input to division
* @return Result after multiplying the left operand by the scale, and
* executing the division on the right hand input.
*/
function divPrecisely(uint256 x, uint256 y)
internal
pure
returns (uint256)
{
// e.g. 8e18 * 1e18 = 8e36
uint256 z = x.mul(FULL_SCALE);
// e.g. 8e36 / 10e18 = 8e17
return z.div(y);
}
/***************************************
RATIO FUNCS
****************************************/
/**
* @dev Multiplies and truncates a token ratio, essentially flooring the result
* i.e. How much mAsset is this bAsset worth?
* @param x Left hand operand to multiplication (i.e Exact quantity)
* @param ratio bAsset ratio
* @return Result after multiplying the two inputs and then dividing by the ratio scale
*/
function mulRatioTruncate(uint256 x, uint256 ratio)
internal
pure
returns (uint256 c)
{
return mulTruncateScale(x, ratio, RATIO_SCALE);
}
/**
* @dev Multiplies and truncates a token ratio, rounding up the result
* i.e. How much mAsset is this bAsset worth?
* @param x Left hand input to multiplication (i.e Exact quantity)
* @param ratio bAsset ratio
* @return Result after multiplying the two inputs and then dividing by the shared
* ratio scale, rounded up to the closest base unit.
*/
function mulRatioTruncateCeil(uint256 x, uint256 ratio)
internal
pure
returns (uint256)
{
// e.g. How much mAsset should I burn for this bAsset (x)?
// 1e18 * 1e8 = 1e26
uint256 scaled = x.mul(ratio);
// 1e26 + 9.99e7 = 100..00.999e8
uint256 ceil = scaled.add(RATIO_SCALE.sub(1));
// return 100..00.999e8 / 1e8 = 1e18
return ceil.div(RATIO_SCALE);
}
/**
* @dev Precisely divides two ratioed units, by first scaling the left hand operand
* i.e. How much bAsset is this mAsset worth?
* @param x Left hand operand in division
* @param ratio bAsset ratio
* @return Result after multiplying the left operand by the scale, and
* executing the division on the right hand input.
*/
function divRatioPrecisely(uint256 x, uint256 ratio)
internal
pure
returns (uint256 c)
{
// e.g. 1e14 * 1e8 = 1e22
uint256 y = x.mul(RATIO_SCALE);
// return 1e22 / 1e12 = 1e10
return y.div(ratio);
}
/***************************************
HELPERS
****************************************/
/**
* @dev Calculates minimum of two numbers
* @param x Left hand input
* @param y Right hand input
* @return Minimum of the two inputs
*/
function min(uint256 x, uint256 y)
internal
pure
returns (uint256)
{
return x > y ? y : x;
}
/**
* @dev Calculated maximum of two numbers
* @param x Left hand input
* @param y Right hand input
* @return Maximum of the two inputs
*/
function max(uint256 x, uint256 y)
internal
pure
returns (uint256)
{
return x > y ? x : y;
}
/**
* @dev Clamps a value to an upper bound
* @param x Left hand input
* @param upperBound Maximum possible value to return
* @return Input x clamped to a maximum value, upperBound
*/
function clamp(uint256 x, uint256 upperBound)
internal
pure
returns (uint256)
{
return x > upperBound ? upperBound : x;
}
}
// External
// Internal
// Libs
/**
* @title SavingsManager
* @author Stability Labs Pty. Ltd.
* @notice Savings Manager collects interest from mAssets and sends them to the
* corresponding Savings Contract, performing some validation in the process.
* @dev VERSION: 1.1
* DATE: 2020-07-29
*/
contract SavingsManager is ISavingsManager, PausableModule {
using SafeMath for uint256;
using StableMath for uint256;
using SafeERC20 for IERC20;
// Core admin events
event SavingsContractAdded(address indexed mAsset, address savingsContract);
event SavingsContractUpdated(address indexed mAsset, address savingsContract);
event SavingsRateChanged(uint256 newSavingsRate);
// Interest collection
event LiquidatorDeposited(address indexed mAsset, uint256 amount);
event InterestCollected(address indexed mAsset, uint256 interest, uint256 newTotalSupply, uint256 apy);
event InterestDistributed(address indexed mAsset, uint256 amountSent);
event InterestWithdrawnByGovernor(address indexed mAsset, address recipient, uint256 amount);
// Locations of each mAsset savings contract
mapping(address => ISavingsContract) public savingsContracts;
// Time at which last collection was made
mapping(address => uint256) public lastPeriodStart;
mapping(address => uint256) public lastCollection;
mapping(address => uint256) public periodYield;
// Amount of collected interest that will be sent to Savings Contract (100%)
uint256 private savingsRate = 1e18;
// Utils to help keep interest under check
uint256 constant private SECONDS_IN_YEAR = 365 days;
// Theoretical cap on APY to avoid excess inflation
uint256 constant private MAX_APY = 15e18;
uint256 constant private TEN_BPS = 1e15;
uint256 constant private THIRTY_MINUTES = 30 minutes;
// Streaming liquidated tokens to SAVE
uint256 private constant DURATION = 7 days;
// Timestamp for current period finish
mapping(address => uint256) public rewardEnd;
mapping(address => uint256) public rewardRate;
constructor(
address _nexus,
address _mUSD,
address _savingsContract
)
public
PausableModule(_nexus)
{
_updateSavingsContract(_mUSD, _savingsContract);
emit SavingsContractAdded(_mUSD, _savingsContract);
}
modifier onlyLiquidator() {
require(msg.sender == _liquidator(), "Only liquidator can execute");
_;
}
/***************************************
STATE
****************************************/
/**
* @dev Adds a new savings contract
* @param _mAsset Address of underlying mAsset
* @param _savingsContract Address of the savings contract
*/
function addSavingsContract(address _mAsset, address _savingsContract)
external
onlyGovernor
{
require(address(savingsContracts[_mAsset]) == address(0), "Savings contract already exists");
_updateSavingsContract(_mAsset, _savingsContract);
emit SavingsContractAdded(_mAsset, _savingsContract);
}
/**
* @dev Updates an existing savings contract
* @param _mAsset Address of underlying mAsset
* @param _savingsContract Address of the savings contract
*/
function updateSavingsContract(address _mAsset, address _savingsContract)
external
onlyGovernor
{
require(address(savingsContracts[_mAsset]) != address(0), "Savings contract does not exist");
_updateSavingsContract(_mAsset, _savingsContract);
emit SavingsContractUpdated(_mAsset, _savingsContract);
}
function _updateSavingsContract(address _mAsset, address _savingsContract)
internal
{
require(_mAsset != address(0) && _savingsContract != address(0), "Must be valid address");
savingsContracts[_mAsset] = ISavingsContract(_savingsContract);
IERC20(_mAsset).safeApprove(address(_savingsContract), 0);
IERC20(_mAsset).safeApprove(address(_savingsContract), uint256(-1));
}
/**
* @dev Sets a new savings rate for interest distribution
* @param _savingsRate Rate of savings sent to SavingsContract (100% = 1e18)
*/
function setSavingsRate(uint256 _savingsRate)
external
onlyGovernor
{
// Greater than 90% upto 100%
require(_savingsRate > 9e17 && _savingsRate <= 1e18, "Must be a valid rate");
savingsRate = _savingsRate;
emit SavingsRateChanged(_savingsRate);
}
/**
* @dev Allows the liquidator to deposit proceeds from iquidated gov tokens.
* Transfers proceeds on a second by second basis to the Savings Contract over 1 week.
* @param _mAsset The mAsset to transfer and distribute
* @param _liquidated Units of mAsset to distribute
*/
function depositLiquidation(address _mAsset, uint256 _liquidated)
external
onlyLiquidator
{
// transfer liquidated mUSD to here
IERC20(_mAsset).safeTransferFrom(_liquidator(), address(this), _liquidated);
uint256 currentTime = now;
// Get remaining rewards
uint256 end = rewardEnd[_mAsset];
uint256 lastUpdate = lastCollection[_mAsset];
uint256 unclaimedSeconds = 0;
if(currentTime <= end || lastUpdate < end){
unclaimedSeconds = end.sub(lastUpdate);
}
uint256 leftover = unclaimedSeconds.mul(rewardRate[_mAsset]);
// Distribute reward per second over 7 days
rewardRate[_mAsset] = _liquidated.add(leftover).div(DURATION);
rewardEnd[_mAsset] = currentTime.add(DURATION);
// Reset pool data to enable lastCollection usage twice
lastPeriodStart[_mAsset] = currentTime;
lastCollection[_mAsset] = currentTime;
periodYield[_mAsset] = 0;
emit LiquidatorDeposited(_mAsset, _liquidated);
}
/***************************************
COLLECTION
****************************************/
/**
* @dev Collects interest from a target mAsset and distributes to the SavingsContract.
* Applies constraints such that the max APY since the last fee collection cannot
* exceed the "MAX_APY" variable.
* @param _mAsset mAsset for which the interest should be collected
*/
function collectAndDistributeInterest(address _mAsset)
external
whenNotPaused
{
ISavingsContract savingsContract = savingsContracts[_mAsset];
require(address(savingsContract) != address(0), "Must have a valid savings contract");
// Get collection details
uint256 recentPeriodStart = lastPeriodStart[_mAsset];
uint256 previousCollection = lastCollection[_mAsset];
lastCollection[_mAsset] = now;
// 1. Collect the new interest from the mAsset
IMasset mAsset = IMasset(_mAsset);
(uint256 interestCollected, uint256 totalSupply) = mAsset.collectInterest();
// 2. Update all the time stamps
// Avoid division by 0 by adding a minimum elapsed time of 1 second
uint256 timeSincePeriodStart = StableMath.max(1, now.sub(recentPeriodStart));
uint256 timeSinceLastCollection = StableMath.max(1, now.sub(previousCollection));
uint256 inflationOperand = interestCollected;
// If it has been 30 mins since last collection, reset period data
if(timeSinceLastCollection > THIRTY_MINUTES) {
lastPeriodStart[_mAsset] = now;
periodYield[_mAsset] = 0;
}
// Else if period has elapsed, start a new period from the lastCollection time
else if(timeSincePeriodStart > THIRTY_MINUTES) {
lastPeriodStart[_mAsset] = previousCollection;
periodYield[_mAsset] = interestCollected;
}
// Else add yield to period yield
else {
inflationOperand = periodYield[_mAsset].add(interestCollected);
periodYield[_mAsset] = inflationOperand;
}
// Add on liquidated
uint256 newReward = _unclaimedRewards(_mAsset, previousCollection);
// 3. Validate that interest is collected correctly and does not exceed max APY
if(interestCollected > 0 || newReward > 0) {
require(
IERC20(_mAsset).balanceOf(address(this)) >= interestCollected.add(newReward),
"Must receive mUSD"
);
uint256 extrapolatedAPY = _validateCollection(totalSupply, inflationOperand, timeSinceLastCollection);
emit InterestCollected(_mAsset, interestCollected, totalSupply, extrapolatedAPY);
// 4. Distribute the interest
// Calculate the share for savers (95e16 or 95%)
uint256 saversShare = interestCollected.mulTruncate(savingsRate);
// Call depositInterest on contract
savingsContract.depositInterest(saversShare.add(newReward));
emit InterestDistributed(_mAsset, saversShare.add(newReward));
} else {
emit InterestCollected(_mAsset, 0, totalSupply, 0);
}
}
/**
* @dev Calculates unclaimed rewards from the liquidation stream
* @param _mAsset mAsset key
* @param _previousCollection Time of previous collection
* @return Units of mAsset that have been unlocked for distribution
*/
function _unclaimedRewards(address _mAsset, uint256 _previousCollection) internal view returns (uint256) {
uint256 end = rewardEnd[_mAsset];
uint256 unclaimedSeconds = _unclaimedSeconds(_previousCollection, end);
return unclaimedSeconds.mul(rewardRate[_mAsset]);
}
/**
* @dev Calculates the seconds of unclaimed rewards, based on period length
* @param _lastUpdate Time of last update
* @param _end End time of period
* @return Seconds of stream that should be compensated
*/
function _unclaimedSeconds(uint256 _lastUpdate, uint256 _end) internal view returns (uint256) {
uint256 currentTime = block.timestamp;
uint256 unclaimedSeconds = 0;
if(currentTime <= _end){
unclaimedSeconds = currentTime.sub(_lastUpdate);
} else if(_lastUpdate < _end) {
unclaimedSeconds = _end.sub(_lastUpdate);
}
return unclaimedSeconds;
}
/**
* @dev Validates that an interest collection does not exceed a maximum APY. If last collection
* was under 30 mins ago, simply check it does not exceed 10bps
* @param _newSupply New total supply of the mAsset
* @param _interest Increase in total supply since last collection
* @param _timeSinceLastCollection Seconds since last collection
*/
function _validateCollection(uint256 _newSupply, uint256 _interest, uint256 _timeSinceLastCollection)
internal
pure
returns (uint256 extrapolatedAPY)
{
// Percentage increase in total supply
// e.g. (1e20 * 1e18) / 1e24 = 1e14 (or a 0.01% increase)
// e.g. (5e18 * 1e18) / 1.2e24 = 4.1667e12
// e.g. (1e19 * 1e18) / 1e21 = 1e16
uint256 oldSupply = _newSupply.sub(_interest);
uint256 percentageIncrease = _interest.divPrecisely(oldSupply);
// If over 30 mins, extrapolate APY
// e.g. day: (86400 * 1e18) / 3.154e7 = 2.74..e15
// e.g. 30 mins: (1800 * 1e18) / 3.154e7 = 5.7..e13
// e.g. epoch: (1593596907 * 1e18) / 3.154e7 = 50.4..e18
uint256 yearsSinceLastCollection =
_timeSinceLastCollection.divPrecisely(SECONDS_IN_YEAR);
// e.g. 0.01% (1e14 * 1e18) / 2.74..e15 = 3.65e16 or 3.65% apr
// e.g. (4.1667e12 * 1e18) / 5.7..e13 = 7.1e16 or 7.1% apr
// e.g. (1e16 * 1e18) / 50e18 = 2e14
extrapolatedAPY = percentageIncrease.divPrecisely(yearsSinceLastCollection);
if(_timeSinceLastCollection > THIRTY_MINUTES) {
require(extrapolatedAPY < MAX_APY, "Interest protected from inflating past maxAPY");
} else {
require(percentageIncrease < TEN_BPS, "Interest protected from inflating past 10 Bps");
}
}
/***************************************
MANAGEMENT
****************************************/
/**
* @dev Withdraws any unallocated interest, i.e. that which has been saved for use
* elsewhere in the system, based on the savingsRate
* @param _mAsset mAsset to collect from
* @param _recipient Address of mAsset recipient
*/
function withdrawUnallocatedInterest(address _mAsset, address _recipient)
external
onlyGovernance
{
IERC20 mAsset = IERC20(_mAsset);
uint256 balance = mAsset.balanceOf(address(this));
emit InterestWithdrawnByGovernor(_mAsset, _recipient, balance);
mAsset.safeTransfer(_recipient, balance);
}
}
{
"compilationTarget": {
"SavingsManager.sol": "SavingsManager"
},
"evmVersion": "istanbul",
"libraries": {},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_nexus","type":"address"},{"internalType":"address","name":"_mUSD","type":"address"},{"internalType":"address","name":"_savingsContract","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"mAsset","type":"address"},{"indexed":false,"internalType":"uint256","name":"interest","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newTotalSupply","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"apy","type":"uint256"}],"name":"InterestCollected","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"mAsset","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountSent","type":"uint256"}],"name":"InterestDistributed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"mAsset","type":"address"},{"indexed":false,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"InterestWithdrawnByGovernor","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"mAsset","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"LiquidatorDeposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"mAsset","type":"address"},{"indexed":false,"internalType":"address","name":"savingsContract","type":"address"}],"name":"SavingsContractAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"mAsset","type":"address"},{"indexed":false,"internalType":"address","name":"savingsContract","type":"address"}],"name":"SavingsContractUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newSavingsRate","type":"uint256"}],"name":"SavingsRateChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"constant":false,"inputs":[{"internalType":"address","name":"_mAsset","type":"address"},{"internalType":"address","name":"_savingsContract","type":"address"}],"name":"addSavingsContract","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_mAsset","type":"address"}],"name":"collectAndDistributeInterest","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_mAsset","type":"address"},{"internalType":"uint256","name":"_liquidated","type":"uint256"}],"name":"depositLiquidation","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"lastCollection","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"lastPeriodStart","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"nexus","outputs":[{"internalType":"contract INexus","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"pause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"periodYield","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rewardEnd","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rewardRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"savingsContracts","outputs":[{"internalType":"contract ISavingsContract","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"_savingsRate","type":"uint256"}],"name":"setSavingsRate","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"unpause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_mAsset","type":"address"},{"internalType":"address","name":"_savingsContract","type":"address"}],"name":"updateSavingsContract","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_mAsset","type":"address"},{"internalType":"address","name":"_recipient","type":"address"}],"name":"withdrawUnallocatedInterest","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}]