Skip to content

Instantly share code, notes, and snippets.

@JamJomJim
Created August 1, 2023 22:01
Show Gist options
  • Save JamJomJim/675fcb6ef59c3edda972894dfd66dfc5 to your computer and use it in GitHub Desktop.
Save JamJomJim/675fcb6ef59c3edda972894dfd66dfc5 to your computer and use it in GitHub Desktop.
Synthetix Contracts. Find it at https://www.cookbook.dev/contracts/Synthetix
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.0;
/**
* @dev Collection of functions related to the address type,
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* This test is non-exhaustive, and there may be false-negatives: during the
* execution of a contract's constructor, its address will be reported as
* not containing a contract.
*
* > It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*/
function isContract(address account) internal view returns (bool) {
// This method relies in extcodesize, which returns 0 for contracts in
// construction, since the code is only stored at the end of the
// constructor execution.
uint256 size;
// solhint-disable-next-line no-inline-assembly
assembly { size := extcodesize(account) }
return size > 0;
}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
// Inheritance
import "./Owned.sol";
import "./IAddressResolver.sol";
// Internal references
import "./IIssuer.sol";
import "./MixinResolver.sol";
// https://docs.synthetix.io/contracts/source/contracts/addressresolver
contract AddressResolver is Owned, IAddressResolver {
mapping(bytes32 => address) public repository;
constructor(address _owner) public Owned(_owner) {}
/* ========== RESTRICTED FUNCTIONS ========== */
function importAddresses(bytes32[] calldata names, address[] calldata destinations) external onlyOwner {
require(names.length == destinations.length, "Input lengths must match");
for (uint i = 0; i < names.length; i++) {
bytes32 name = names[i];
address destination = destinations[i];
repository[name] = destination;
emit AddressImported(name, destination);
}
}
/* ========= PUBLIC FUNCTIONS ========== */
function rebuildCaches(MixinResolver[] calldata destinations) external {
for (uint i = 0; i < destinations.length; i++) {
destinations[i].rebuildCache();
}
}
/* ========== VIEWS ========== */
function areAddressesImported(bytes32[] calldata names, address[] calldata destinations) external view returns (bool) {
for (uint i = 0; i < names.length; i++) {
if (repository[names[i]] != destinations[i]) {
return false;
}
}
return true;
}
function getAddress(bytes32 name) external view returns (address) {
return repository[name];
}
function requireAndGetAddress(bytes32 name, string calldata reason) external view returns (address) {
address _foundAddress = repository[name];
require(_foundAddress != address(0), reason);
return _foundAddress;
}
function getSynth(bytes32 key) external view returns (address) {
IIssuer issuer = IIssuer(repository["Issuer"]);
require(address(issuer) != address(0), "Cannot find Issuer address");
return address(issuer.synths(key));
}
/* ========== EVENTS ========== */
event AddressImported(bytes32 name, address destination);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
// https://docs.synthetix.io/contracts/source/libraries/addresssetlib/
library AddressSetLib {
struct AddressSet {
address[] elements;
mapping(address => uint) indices;
}
function contains(AddressSet storage set, address candidate) internal view returns (bool) {
if (set.elements.length == 0) {
return false;
}
uint index = set.indices[candidate];
return index != 0 || set.elements[0] == candidate;
}
function getPage(
AddressSet storage set,
uint index,
uint pageSize
) internal view returns (address[] memory) {
// NOTE: This implementation should be converted to slice operators if the compiler is updated to v0.6.0+
uint endIndex = index + pageSize; // The check below that endIndex <= index handles overflow.
// If the page extends past the end of the list, truncate it.
if (endIndex > set.elements.length) {
endIndex = set.elements.length;
}
if (endIndex <= index) {
return new address[](0);
}
uint n = endIndex - index; // We already checked for negative overflow.
address[] memory page = new address[](n);
for (uint i; i < n; i++) {
page[i] = set.elements[i + index];
}
return page;
}
function add(AddressSet storage set, address element) internal {
// Adding to a set is an idempotent operation.
if (!contains(set, element)) {
set.indices[element] = set.elements.length;
set.elements.push(element);
}
}
function remove(AddressSet storage set, address element) internal {
require(contains(set, element), "Element not in set.");
// Replace the removed element with the last element of the list.
uint index = set.indices[element];
uint lastIndex = set.elements.length - 1; // We required that element is in the list, so it is not empty.
if (index != lastIndex) {
// No need to shift the last element if it is the one we want to delete.
address shiftedElement = set.elements[lastIndex];
set.elements[index] = shiftedElement;
set.indices[shiftedElement] = index;
}
set.elements.pop();
delete set.indices[element];
}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.5.0;
interface AggregatorInterface {
function latestAnswer() external view returns (int256);
function latestTimestamp() external view returns (uint256);
function latestRound() external view returns (uint256);
function getAnswer(uint256 roundId) external view returns (int256);
function getTimestamp(uint256 roundId) external view returns (uint256);
event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 timestamp);
event NewRound(uint256 indexed roundId, address indexed startedBy, uint256 startedAt);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
//import "@chainlink/contracts-0.0.10/src/v0.5/interfaces/AggregatorV2V3Interface.sol";
interface AggregatorV2V3Interface {
function latestRound() external view returns (uint256);
function decimals() external view returns (uint8);
function getAnswer(uint256 roundId) external view returns (int256);
function getTimestamp(uint256 roundId) external view returns (uint256);
function getRoundData(uint80 _roundId)
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
function latestRoundData()
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.5.0;
interface AggregatorV3Interface {
function decimals() external view returns (uint8);
function description() external view returns (string memory);
function version() external view returns (uint256);
// getRoundData and latestRoundData should both raise "No data present"
// if they do not have data to report, instead of returning unset values
// which could be misinterpreted as actual reported values.
function getRoundData(uint80 _roundId)
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
function latestRoundData()
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
// Inheritance
import "./Owned.sol";
import "./MixinResolver.sol";
import "./MixinSystemSettings.sol";
import "./IDebtCache.sol";
// Libraries
import "./SafeDecimalMath.sol";
// Internal references
import "./IIssuer.sol";
import "./IExchanger.sol";
import "./IExchangeRates.sol";
import "./ISystemStatus.sol";
import "./IERC20.sol";
import "./ICollateralManager.sol";
import "./IEtherWrapper.sol";
import "./IWrapperFactory.sol";
import "./IFuturesMarketManager.sol";
// https://docs.synthetix.io/contracts/source/contracts/debtcache
contract BaseDebtCache is Owned, MixinSystemSettings, IDebtCache {
using SafeMath for uint;
using SafeDecimalMath for uint;
uint internal _cachedDebt;
mapping(bytes32 => uint) internal _cachedSynthDebt;
mapping(bytes32 => uint) internal _excludedIssuedDebt;
uint internal _cacheTimestamp;
bool internal _cacheInvalid = true;
// flag to ensure importing excluded debt is invoked only once
bool public isInitialized = false; // public to avoid needing an event
/* ========== ENCODED NAMES ========== */
bytes32 internal constant sUSD = "sUSD";
bytes32 internal constant sETH = "sETH";
/* ========== ADDRESS RESOLVER CONFIGURATION ========== */
bytes32 private constant CONTRACT_ISSUER = "Issuer";
bytes32 private constant CONTRACT_EXCHANGER = "Exchanger";
bytes32 private constant CONTRACT_EXRATES = "ExchangeRates";
bytes32 private constant CONTRACT_SYSTEMSTATUS = "SystemStatus";
bytes32 private constant CONTRACT_COLLATERALMANAGER = "CollateralManager";
bytes32 private constant CONTRACT_ETHER_WRAPPER = "EtherWrapper";
bytes32 private constant CONTRACT_FUTURESMARKETMANAGER = "FuturesMarketManager";
bytes32 private constant CONTRACT_WRAPPER_FACTORY = "WrapperFactory";
constructor(address _owner, address _resolver) public Owned(_owner) MixinSystemSettings(_resolver) {}
/* ========== VIEWS ========== */
function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
bytes32[] memory existingAddresses = MixinSystemSettings.resolverAddressesRequired();
bytes32[] memory newAddresses = new bytes32[](8);
newAddresses[0] = CONTRACT_ISSUER;
newAddresses[1] = CONTRACT_EXCHANGER;
newAddresses[2] = CONTRACT_EXRATES;
newAddresses[3] = CONTRACT_SYSTEMSTATUS;
newAddresses[4] = CONTRACT_COLLATERALMANAGER;
newAddresses[5] = CONTRACT_WRAPPER_FACTORY;
newAddresses[6] = CONTRACT_ETHER_WRAPPER;
newAddresses[7] = CONTRACT_FUTURESMARKETMANAGER;
addresses = combineArrays(existingAddresses, newAddresses);
}
function issuer() internal view returns (IIssuer) {
return IIssuer(requireAndGetAddress(CONTRACT_ISSUER));
}
function exchanger() internal view returns (IExchanger) {
return IExchanger(requireAndGetAddress(CONTRACT_EXCHANGER));
}
function exchangeRates() internal view returns (IExchangeRates) {
return IExchangeRates(requireAndGetAddress(CONTRACT_EXRATES));
}
function systemStatus() internal view returns (ISystemStatus) {
return ISystemStatus(requireAndGetAddress(CONTRACT_SYSTEMSTATUS));
}
function collateralManager() internal view returns (ICollateralManager) {
return ICollateralManager(requireAndGetAddress(CONTRACT_COLLATERALMANAGER));
}
function etherWrapper() internal view returns (IEtherWrapper) {
return IEtherWrapper(requireAndGetAddress(CONTRACT_ETHER_WRAPPER));
}
function futuresMarketManager() internal view returns (IFuturesMarketManager) {
return IFuturesMarketManager(requireAndGetAddress(CONTRACT_FUTURESMARKETMANAGER));
}
function wrapperFactory() internal view returns (IWrapperFactory) {
return IWrapperFactory(requireAndGetAddress(CONTRACT_WRAPPER_FACTORY));
}
function debtSnapshotStaleTime() external view returns (uint) {
return getDebtSnapshotStaleTime();
}
function cachedDebt() external view returns (uint) {
return _cachedDebt;
}
function cachedSynthDebt(bytes32 currencyKey) external view returns (uint) {
return _cachedSynthDebt[currencyKey];
}
function cacheTimestamp() external view returns (uint) {
return _cacheTimestamp;
}
function cacheInvalid() external view returns (bool) {
return _cacheInvalid;
}
function _cacheStale(uint timestamp) internal view returns (bool) {
// Note a 0 timestamp means that the cache is uninitialised.
// We'll keep the check explicitly in case the stale time is
// ever set to something higher than the current unix time (e.g. to turn off staleness).
return getDebtSnapshotStaleTime() < block.timestamp - timestamp || timestamp == 0;
}
function cacheStale() external view returns (bool) {
return _cacheStale(_cacheTimestamp);
}
function _issuedSynthValues(bytes32[] memory currencyKeys, uint[] memory rates)
internal
view
returns (uint[] memory values)
{
uint numValues = currencyKeys.length;
values = new uint[](numValues);
ISynth[] memory synths = issuer().getSynths(currencyKeys);
for (uint i = 0; i < numValues; i++) {
address synthAddress = address(synths[i]);
require(synthAddress != address(0), "Synth does not exist");
uint supply = IERC20(synthAddress).totalSupply();
values[i] = supply.multiplyDecimalRound(rates[i]);
}
return (values);
}
function _currentSynthDebts(bytes32[] memory currencyKeys)
internal
view
returns (
uint[] memory snxIssuedDebts,
uint _futuresDebt,
uint _excludedDebt,
bool anyRateIsInvalid
)
{
(uint[] memory rates, bool isInvalid) = exchangeRates().ratesAndInvalidForCurrencies(currencyKeys);
uint[] memory values = _issuedSynthValues(currencyKeys, rates);
(uint excludedDebt, bool isAnyNonSnxDebtRateInvalid) = _totalNonSnxBackedDebt(currencyKeys, rates, isInvalid);
(uint futuresDebt, bool futuresDebtIsInvalid) = futuresMarketManager().totalDebt();
return (values, futuresDebt, excludedDebt, isInvalid || futuresDebtIsInvalid || isAnyNonSnxDebtRateInvalid);
}
function currentSynthDebts(bytes32[] calldata currencyKeys)
external
view
returns (
uint[] memory debtValues,
uint futuresDebt,
uint excludedDebt,
bool anyRateIsInvalid
)
{
return _currentSynthDebts(currencyKeys);
}
function _cachedSynthDebts(bytes32[] memory currencyKeys) internal view returns (uint[] memory) {
uint numKeys = currencyKeys.length;
uint[] memory debts = new uint[](numKeys);
for (uint i = 0; i < numKeys; i++) {
debts[i] = _cachedSynthDebt[currencyKeys[i]];
}
return debts;
}
function cachedSynthDebts(bytes32[] calldata currencyKeys) external view returns (uint[] memory snxIssuedDebts) {
return _cachedSynthDebts(currencyKeys);
}
function _excludedIssuedDebts(bytes32[] memory currencyKeys) internal view returns (uint[] memory) {
uint numKeys = currencyKeys.length;
uint[] memory debts = new uint[](numKeys);
for (uint i = 0; i < numKeys; i++) {
debts[i] = _excludedIssuedDebt[currencyKeys[i]];
}
return debts;
}
function excludedIssuedDebts(bytes32[] calldata currencyKeys) external view returns (uint[] memory excludedDebts) {
return _excludedIssuedDebts(currencyKeys);
}
/// used when migrating to new DebtCache instance in order to import the excluded debt records
/// If this method is not run after upgrading the contract, the debt will be
/// incorrect w.r.t to wrapper factory assets until the values are imported from
/// previous instance of the contract
/// Also, in addition to this method it's possible to use recordExcludedDebtChange since
/// it's accessible to owner in case additional adjustments are required
function importExcludedIssuedDebts(IDebtCache prevDebtCache, IIssuer prevIssuer) external onlyOwner {
// this can only be run once so that recorded debt deltas aren't accidentally
// lost or double counted
require(!isInitialized, "already initialized");
isInitialized = true;
// get the currency keys from **previous** issuer, in case current issuer
// doesn't have all the synths at this point
// warning: if a synth won't be added to the current issuer before the next upgrade of this contract,
// its entry will be lost (because it won't be in the prevIssuer for next time).
// if for some reason this is a problem, it should be possible to use recordExcludedDebtChange() to amend
bytes32[] memory keys = prevIssuer.availableCurrencyKeys();
require(keys.length > 0, "previous Issuer has no synths");
// query for previous debt records
uint[] memory debts = prevDebtCache.excludedIssuedDebts(keys);
// store the values
for (uint i = 0; i < keys.length; i++) {
if (debts[i] > 0) {
// adding the values instead of overwriting in case some deltas were recorded in this
// contract already (e.g. if the upgrade was not atomic)
_excludedIssuedDebt[keys[i]] = _excludedIssuedDebt[keys[i]].add(debts[i]);
}
}
}
// Returns the total sUSD debt backed by non-SNX collateral.
function totalNonSnxBackedDebt() external view returns (uint excludedDebt, bool isInvalid) {
bytes32[] memory currencyKeys = issuer().availableCurrencyKeys();
(uint[] memory rates, bool ratesAreInvalid) = exchangeRates().ratesAndInvalidForCurrencies(currencyKeys);
return _totalNonSnxBackedDebt(currencyKeys, rates, ratesAreInvalid);
}
function _totalNonSnxBackedDebt(
bytes32[] memory currencyKeys,
uint[] memory rates,
bool ratesAreInvalid
) internal view returns (uint excludedDebt, bool isInvalid) {
// Calculate excluded debt.
// 1. MultiCollateral long debt + short debt.
(uint longValue, bool anyTotalLongRateIsInvalid) = collateralManager().totalLong();
(uint shortValue, bool anyTotalShortRateIsInvalid) = collateralManager().totalShort();
isInvalid = ratesAreInvalid || anyTotalLongRateIsInvalid || anyTotalShortRateIsInvalid;
excludedDebt = longValue.add(shortValue);
// 2. EtherWrapper.
// Subtract sETH and sUSD issued by EtherWrapper.
excludedDebt = excludedDebt.add(etherWrapper().totalIssuedSynths());
// 3. WrapperFactory.
// Get the debt issued by the Wrappers.
for (uint i = 0; i < currencyKeys.length; i++) {
excludedDebt = excludedDebt.add(_excludedIssuedDebt[currencyKeys[i]].multiplyDecimalRound(rates[i]));
}
return (excludedDebt, isInvalid);
}
function _currentDebt() internal view returns (uint debt, bool anyRateIsInvalid) {
bytes32[] memory currencyKeys = issuer().availableCurrencyKeys();
(uint[] memory rates, bool isInvalid) = exchangeRates().ratesAndInvalidForCurrencies(currencyKeys);
// Sum all issued synth values based on their supply.
uint[] memory values = _issuedSynthValues(currencyKeys, rates);
(uint excludedDebt, bool isAnyNonSnxDebtRateInvalid) = _totalNonSnxBackedDebt(currencyKeys, rates, isInvalid);
uint numValues = values.length;
uint total;
for (uint i; i < numValues; i++) {
total = total.add(values[i]);
}
// Add in the debt accounted for by futures
(uint futuresDebt, bool futuresDebtIsInvalid) = futuresMarketManager().totalDebt();
total = total.add(futuresDebt);
// Ensure that if the excluded non-SNX debt exceeds SNX-backed debt, no overflow occurs
total = total < excludedDebt ? 0 : total.sub(excludedDebt);
return (total, isInvalid || futuresDebtIsInvalid || isAnyNonSnxDebtRateInvalid);
}
function currentDebt() external view returns (uint debt, bool anyRateIsInvalid) {
return _currentDebt();
}
function cacheInfo()
external
view
returns (
uint debt,
uint timestamp,
bool isInvalid,
bool isStale
)
{
uint time = _cacheTimestamp;
return (_cachedDebt, time, _cacheInvalid, _cacheStale(time));
}
/* ========== MUTATIVE FUNCTIONS ========== */
// Stub out all mutative functions as no-ops;
// since they do nothing, there are no restrictions
function updateCachedSynthDebts(bytes32[] calldata currencyKeys) external {}
function updateCachedSynthDebtWithRate(bytes32 currencyKey, uint currencyRate) external {}
function updateCachedSynthDebtsWithRates(bytes32[] calldata currencyKeys, uint[] calldata currencyRates) external {}
function updateDebtCacheValidity(bool currentlyInvalid) external {}
function purgeCachedSynthDebt(bytes32 currencyKey) external {}
function takeDebtSnapshot() external {}
function recordExcludedDebtChange(bytes32 currencyKey, int256 delta) external {}
function updateCachedsUSDDebt(int amount) external {}
/* ========== MODIFIERS ========== */
function _requireSystemActiveIfNotOwner() internal view {
if (msg.sender != owner) {
systemStatus().requireSystemActive();
}
}
modifier requireSystemActiveIfNotOwner() {
_requireSystemActiveIfNotOwner();
_;
}
function _onlyIssuer() internal view {
require(msg.sender == address(issuer()), "Sender is not Issuer");
}
modifier onlyIssuer() {
_onlyIssuer();
_;
}
function _onlyIssuerOrExchanger() internal view {
require(msg.sender == address(issuer()) || msg.sender == address(exchanger()), "Sender is not Issuer or Exchanger");
}
modifier onlyIssuerOrExchanger() {
_onlyIssuerOrExchanger();
_;
}
function _onlyDebtIssuer() internal view {
bool isWrapper = wrapperFactory().isWrapper(msg.sender);
// owner included for debugging and fixing in emergency situation
bool isOwner = msg.sender == owner;
require(isOwner || isWrapper, "Only debt issuers may call this");
}
modifier onlyDebtIssuer() {
_onlyDebtIssuer();
_;
}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
pragma experimental ABIEncoderV2;
// Inheritance
import "./Owned.sol";
import "./MixinResolver.sol";
import "./MixinSystemSettings.sol";
import "./IBaseSynthetixBridge.sol";
// Libraries
import "./Math.sol";
import "./SafeDecimalMath.sol";
import "./SafeERC20.sol";
// Internal references
import "./ISynthetix.sol";
import "./IRewardEscrowV2.sol";
import "./IIssuer.sol";
import "./iAbs_BaseCrossDomainMessenger.sol";
contract BaseDebtMigrator is Owned, MixinSystemSettings {
using SafeMath for uint;
using SafeDecimalMath for uint;
using SafeERC20 for IERC20;
// have to define this function like this here because contract name is required for FlexibleStorage
function CONTRACT_NAME() public pure returns (bytes32);
/* ========== ADDRESS RESOLVER CONFIGURATION ========== */
bytes32 private constant CONTRACT_EXT_MESSENGER = "ext:Messenger";
bytes32 private constant CONTRACT_ISSUER = "Issuer";
bytes32 internal constant CONTRACT_SYNTHETIX = "Synthetix";
bytes32 private constant CONTRACT_REWARDESCROW = "RewardEscrowV2";
bytes32 private constant DEBT_TRANSFER_NAMESPACE = "DebtTransfer";
bytes32 internal constant DEBT_TRANSFER_SENT = "Sent";
bytes32 internal constant DEBT_TRANSFER_RECV = "Recv";
bytes32 internal constant sUSD = "sUSD";
bytes32 internal constant SDS = "SDS";
/* ========== CONSTRUCTOR ========= */
constructor(address _owner, address _resolver) public Owned(_owner) MixinSystemSettings(_resolver) {}
/* ========== VIEWS ========== */
function _issuer() internal view returns (IIssuer) {
return IIssuer(requireAndGetAddress(CONTRACT_ISSUER));
}
function _messenger() internal view returns (iAbs_BaseCrossDomainMessenger) {
return iAbs_BaseCrossDomainMessenger(requireAndGetAddress(CONTRACT_EXT_MESSENGER));
}
function _rewardEscrowV2() internal view returns (IRewardEscrowV2) {
return IRewardEscrowV2(requireAndGetAddress(CONTRACT_REWARDESCROW));
}
function _synthetixERC20() internal view returns (IERC20) {
return IERC20(requireAndGetAddress(CONTRACT_SYNTHETIX));
}
function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
bytes32[] memory existingAddresses = MixinSystemSettings.resolverAddressesRequired();
bytes32[] memory newAddresses = new bytes32[](4);
newAddresses[0] = CONTRACT_EXT_MESSENGER;
newAddresses[1] = CONTRACT_REWARDESCROW;
newAddresses[2] = CONTRACT_ISSUER;
newAddresses[3] = CONTRACT_SYNTHETIX;
addresses = combineArrays(existingAddresses, newAddresses);
}
/* ======== INTERNALS ======== */
function debtTransferSent() external view returns (uint) {
bytes32 debtSharesKey = keccak256(abi.encodePacked(DEBT_TRANSFER_NAMESPACE, DEBT_TRANSFER_SENT, SDS));
uint currentDebtShares = flexibleStorage().getUIntValue(CONTRACT_NAME(), debtSharesKey);
return currentDebtShares;
}
function debtTransferReceived() external view returns (uint) {
bytes32 debtSharesKey = keccak256(abi.encodePacked(DEBT_TRANSFER_NAMESPACE, DEBT_TRANSFER_RECV, SDS));
uint currentDebtShares = flexibleStorage().getUIntValue(CONTRACT_NAME(), debtSharesKey);
return currentDebtShares;
}
function _incrementDebtTransferCounter(bytes32 group, uint debtShares) internal {
bytes32 debtSharesKey = keccak256(abi.encodePacked(DEBT_TRANSFER_NAMESPACE, group, SDS));
uint currentDebtShares = flexibleStorage().getUIntValue(CONTRACT_NAME(), debtSharesKey);
flexibleStorage().setUIntValue(CONTRACT_NAME(), debtSharesKey, currentDebtShares.add(debtShares));
}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
import "./Owned.sol";
contract BaseMigration is Owned {
constructor(address _owner) internal Owned(_owner) {}
// safety value to return ownership (anyone can invoke)
function returnOwnership(address forContract) public {
bytes memory payload = abi.encodeWithSignature("nominateNewOwner(address)", owner);
// solhint-disable avoid-low-level-calls
(bool success, ) = forContract.call(payload);
if (!success) {
// then try legacy way
bytes memory legacyPayload = abi.encodeWithSignature("nominateOwner(address)", owner);
// solhint-disable avoid-low-level-calls
(bool legacySuccess, ) = forContract.call(legacyPayload);
require(legacySuccess, "Legacy nomination failed");
}
}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
//import "@chainlink/contracts-0.0.10/src/v0.5/interfaces/AggregatorV2V3Interface.sol";
import "./AddressResolver.sol";
import "./IDebtCache.sol";
import "./ISynthetixDebtShare.sol";
import "./AggregatorV2V3Interface.sol";
import "./SafeDecimalMath.sol";
// aggregator which reports the data from the system itself
// useful for testing
contract BaseOneNetAggregator is Owned, AggregatorV2V3Interface {
using SafeDecimalMath for uint;
AddressResolver public resolver;
uint public overrideTimestamp;
constructor(AddressResolver _resolver) public Owned(msg.sender) {
resolver = _resolver;
}
function setOverrideTimestamp(uint timestamp) public onlyOwner {
overrideTimestamp = timestamp;
emit SetOverrideTimestamp(timestamp);
}
function latestRoundData()
external
view
returns (
uint80,
int256,
uint256,
uint256,
uint80
)
{
return getRoundData(uint80(latestRound()));
}
function latestRound() public view returns (uint256) {
return 1;
}
function decimals() external view returns (uint8) {
return 0;
}
function getAnswer(uint256 _roundId) external view returns (int256 answer) {
(, answer, , , ) = getRoundData(uint80(_roundId));
}
function getTimestamp(uint256 _roundId) external view returns (uint256 timestamp) {
(, , timestamp, , ) = getRoundData(uint80(_roundId));
}
function getRoundData(uint80)
public
view
returns (
uint80,
int256,
uint256,
uint256,
uint80
);
event SetOverrideTimestamp(uint timestamp);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
pragma experimental ABIEncoderV2;
// Inheritance
import "./Owned.sol";
import "./MixinResolver.sol";
import "./RewardEscrowV2Storage.sol";
import "./LimitedSetup.sol";
import "./IRewardEscrowV2.sol";
// Libraries
import "./SafeCast.sol";
import "./SafeDecimalMath.sol";
// Internal references
import "./IERC20.sol";
import "./IFeePool.sol";
import "./IIssuer.sol";
// https://docs.synthetix.io/contracts/RewardEscrow
contract BaseRewardEscrowV2 is Owned, IRewardEscrowV2, LimitedSetup(8 weeks), MixinResolver {
using SafeMath for uint;
using SafeDecimalMath for uint;
/* Mapping of nominated address to recieve account merging */
mapping(address => address) public nominatedReceiver;
/* Max escrow duration */
uint public max_duration = 2 * 52 weeks; // Default max 2 years duration
/* Max account merging duration */
uint public maxAccountMergingDuration = 4 weeks; // Default 4 weeks is max
/* ========== ACCOUNT MERGING CONFIGURATION ========== */
uint public accountMergingDuration = 1 weeks;
uint public accountMergingStartTime;
/* ========== ADDRESS RESOLVER CONFIGURATION ========== */
bytes32 private constant CONTRACT_SYNTHETIX = "Synthetix";
bytes32 private constant CONTRACT_ISSUER = "Issuer";
bytes32 private constant CONTRACT_FEEPOOL = "FeePool";
bytes32 private constant CONTRACT_REWARDESCROWV2STORAGE = "RewardEscrowV2Storage";
/* ========== CONSTRUCTOR ========== */
constructor(address _owner, address _resolver) public Owned(_owner) MixinResolver(_resolver) {}
/* ========== VIEWS ======================= */
function feePool() internal view returns (IFeePool) {
return IFeePool(requireAndGetAddress(CONTRACT_FEEPOOL));
}
function synthetixERC20() internal view returns (IERC20) {
return IERC20(requireAndGetAddress(CONTRACT_SYNTHETIX));
}
function issuer() internal view returns (IIssuer) {
return IIssuer(requireAndGetAddress(CONTRACT_ISSUER));
}
function state() internal view returns (IRewardEscrowV2Storage) {
return IRewardEscrowV2Storage(requireAndGetAddress(CONTRACT_REWARDESCROWV2STORAGE));
}
function _notImplemented() internal pure {
revert("Cannot be run on this layer");
}
/* ========== VIEW FUNCTIONS ========== */
// Note: use public visibility so that it can be invoked in a subclass
function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
addresses = new bytes32[](4);
addresses[0] = CONTRACT_SYNTHETIX;
addresses[1] = CONTRACT_FEEPOOL;
addresses[2] = CONTRACT_ISSUER;
addresses[3] = CONTRACT_REWARDESCROWV2STORAGE;
}
/// views forwarded from storage contract
function numVestingEntries(address account) public view returns (uint) {
return state().numVestingEntries(account);
}
function totalEscrowedBalance() public view returns (uint) {
return state().totalEscrowedBalance();
}
function totalEscrowedAccountBalance(address account) public view returns (uint) {
return state().totalEscrowedAccountBalance(account);
}
function totalVestedAccountBalance(address account) external view returns (uint) {
return state().totalVestedAccountBalance(account);
}
function nextEntryId() external view returns (uint) {
return state().nextEntryId();
}
function vestingSchedules(address account, uint256 entryId) public view returns (VestingEntries.VestingEntry memory) {
return state().vestingSchedules(account, entryId);
}
function accountVestingEntryIDs(address account, uint256 index) public view returns (uint) {
return state().accountVestingEntryIDs(account, index);
}
/**
* @notice A simple alias to totalEscrowedAccountBalance: provides ERC20 balance integration.
*/
function balanceOf(address account) public view returns (uint) {
return totalEscrowedAccountBalance(account);
}
/**
* @notice Get a particular schedule entry for an account.
* @return The vesting entry object and rate per second emission.
*/
function getVestingEntry(address account, uint256 entryID) external view returns (uint64 endTime, uint256 escrowAmount) {
VestingEntries.VestingEntry memory entry = vestingSchedules(account, entryID);
return (entry.endTime, entry.escrowAmount);
}
function getVestingSchedules(
address account,
uint256 index,
uint256 pageSize
) external view returns (VestingEntries.VestingEntryWithID[] memory) {
uint256 endIndex = index + pageSize;
// If index starts after the endIndex return no results
if (endIndex <= index) {
return new VestingEntries.VestingEntryWithID[](0);
}
// If the page extends past the end of the accountVestingEntryIDs, truncate it.
if (endIndex > numVestingEntries(account)) {
endIndex = numVestingEntries(account);
}
uint256 n = endIndex - index;
uint256 entryID;
VestingEntries.VestingEntry memory entry;
VestingEntries.VestingEntryWithID[] memory vestingEntries = new VestingEntries.VestingEntryWithID[](n);
for (uint256 i; i < n; i++) {
entryID = accountVestingEntryIDs(account, i + index);
entry = vestingSchedules(account, entryID);
vestingEntries[i] = VestingEntries.VestingEntryWithID({
endTime: uint64(entry.endTime),
escrowAmount: entry.escrowAmount,
entryID: entryID
});
}
return vestingEntries;
}
function getAccountVestingEntryIDs(
address account,
uint256 index,
uint256 pageSize
) external view returns (uint256[] memory) {
uint256 endIndex = index + pageSize;
// If the page extends past the end of the accountVestingEntryIDs, truncate it.
uint numEntries = numVestingEntries(account);
if (endIndex > numEntries) {
endIndex = numEntries;
}
if (endIndex <= index) {
return new uint256[](0);
}
uint256 n = endIndex - index;
uint256[] memory page = new uint256[](n);
for (uint256 i; i < n; i++) {
page[i] = accountVestingEntryIDs(account, i + index);
}
return page;
}
function getVestingQuantity(address account, uint256[] calldata entryIDs) external view returns (uint total) {
VestingEntries.VestingEntry memory entry;
for (uint i = 0; i < entryIDs.length; i++) {
entry = vestingSchedules(account, entryIDs[i]);
/* Skip entry if escrowAmount == 0 */
if (entry.escrowAmount != 0) {
uint256 quantity = _claimableAmount(entry);
/* add quantity to total */
total = total.add(quantity);
}
}
}
function getVestingEntryClaimable(address account, uint256 entryID) external view returns (uint) {
return _claimableAmount(vestingSchedules(account, entryID));
}
function _claimableAmount(VestingEntries.VestingEntry memory _entry) internal view returns (uint256) {
uint256 quantity;
if (_entry.escrowAmount != 0) {
/* Escrow amounts claimable if block.timestamp equal to or after entry endTime */
quantity = block.timestamp >= _entry.endTime ? _entry.escrowAmount : 0;
}
return quantity;
}
/* ========== MUTATIVE FUNCTIONS ========== */
/**
* Vest escrowed amounts that are claimable
* Allows users to vest their vesting entries based on msg.sender
*/
function vest(uint256[] calldata entryIDs) external {
// only account can call vest
address account = msg.sender;
uint256 total;
VestingEntries.VestingEntry memory entry;
uint256 quantity;
for (uint i = 0; i < entryIDs.length; i++) {
entry = vestingSchedules(account, entryIDs[i]);
/* Skip entry if escrowAmount == 0 already vested */
if (entry.escrowAmount != 0) {
quantity = _claimableAmount(entry);
/* update entry to remove escrowAmount */
if (quantity > 0) {
state().setZeroAmount(account, entryIDs[i]);
}
/* add quantity to total */
total = total.add(quantity);
}
}
/* Transfer vested tokens. Will revert if total > totalEscrowedAccountBalance */
if (total != 0) {
_subtractAndTransfer(account, account, total);
// update total vested
state().updateVestedAccountBalance(account, SafeCast.toInt256(total));
emit Vested(account, block.timestamp, total);
}
}
/// method for revoking vesting entries regardless of schedule to be used for liquidations
/// access controlled to only Synthetix contract
/// @param account: account
/// @param recipient: account to transfer the revoked tokens to
/// @param targetAmount: amount of SNX to revoke, when this amount is reached, no more entries are revoked
/// @param startIndex: index into accountVestingEntryIDs[account] to start iterating from
function revokeFrom(
address account,
address recipient,
uint targetAmount,
uint startIndex
) external onlySynthetix {
require(account != address(0), "account not set");
require(recipient != address(0), "recipient not set");
// set stored entries to zero
(uint total, uint endIndex, uint lastEntryTime) =
state().setZeroAmountUntilTarget(account, startIndex, targetAmount);
// check total is indeed enough
// the caller should have checked for the general amount of escrow
// but only here we check that startIndex results in sufficient amount
require(total >= targetAmount, "entries sum less than target");
// if too much was revoked
if (total > targetAmount) {
// only take the precise amount needed by adding a new entry with the difference from total
uint refund = total.sub(targetAmount);
uint entryID =
state().addVestingEntry(
account,
VestingEntries.VestingEntry({endTime: uint64(lastEntryTime), escrowAmount: refund})
);
// emit event
uint duration = lastEntryTime > block.timestamp ? lastEntryTime.sub(block.timestamp) : 0;
emit VestingEntryCreated(account, block.timestamp, refund, duration, entryID);
}
// update the aggregates and move the tokens
_subtractAndTransfer(account, recipient, targetAmount);
emit Revoked(account, recipient, targetAmount, startIndex, endIndex);
}
/// remove tokens from vesting aggregates and transfer them to recipient
function _subtractAndTransfer(
address subtractFrom,
address transferTo,
uint256 amount
) internal {
state().updateEscrowAccountBalance(subtractFrom, -SafeCast.toInt256(amount));
synthetixERC20().transfer(transferTo, amount);
}
/**
* @notice Create an escrow entry to lock SNX for a given duration in seconds
* @dev This call expects that the depositor (msg.sender) has already approved the Reward escrow contract
to spend the the amount being escrowed.
*/
function createEscrowEntry(
address beneficiary,
uint256 deposit,
uint256 duration
) external {
require(beneficiary != address(0), "Cannot create escrow with address(0)");
/* Transfer SNX from msg.sender */
require(synthetixERC20().transferFrom(msg.sender, address(this), deposit), "token transfer failed");
/* Append vesting entry for the beneficiary address */
_appendVestingEntry(beneficiary, deposit, duration);
}
/**
* @notice Add a new vesting entry at a given time and quantity to an account's schedule.
* @dev A call to this should accompany a previous successful call to synthetix.transfer(rewardEscrow, amount),
* to ensure that when the funds are withdrawn, there is enough balance.
* @param account The account to append a new vesting entry to.
* @param quantity The quantity of SNX that will be escrowed.
* @param duration The duration that SNX will be emitted.
*/
function appendVestingEntry(
address account,
uint256 quantity,
uint256 duration
) external onlyFeePool {
_appendVestingEntry(account, quantity, duration);
}
function _appendVestingEntry(
address account,
uint256 quantity,
uint256 duration
) internal {
/* No empty or already-passed vesting entries allowed. */
require(quantity != 0, "Quantity cannot be zero");
require(duration > 0 && duration <= max_duration, "Cannot escrow with 0 duration OR above max_duration");
// Add quantity to account's escrowed balance to the total balance
state().updateEscrowAccountBalance(account, SafeCast.toInt256(quantity));
/* There must be enough balance in the contract to provide for the vesting entry. */
require(
totalEscrowedBalance() <= synthetixERC20().balanceOf(address(this)),
"Must be enough balance in the contract to provide for the vesting entry"
);
/* Escrow the tokens for duration. */
uint endTime = block.timestamp + duration;
// store vesting entry
uint entryID =
state().addVestingEntry(
account,
VestingEntries.VestingEntry({endTime: uint64(endTime), escrowAmount: quantity})
);
emit VestingEntryCreated(account, block.timestamp, quantity, duration, entryID);
}
/* ========== ACCOUNT MERGING ========== */
function accountMergingIsOpen() public view returns (bool) {
return accountMergingStartTime.add(accountMergingDuration) > block.timestamp;
}
function startMergingWindow() external onlyOwner {
accountMergingStartTime = block.timestamp;
emit AccountMergingStarted(accountMergingStartTime, accountMergingStartTime.add(accountMergingDuration));
}
function setAccountMergingDuration(uint256 duration) external onlyOwner {
require(duration <= maxAccountMergingDuration, "exceeds max merging duration");
accountMergingDuration = duration;
emit AccountMergingDurationUpdated(duration);
}
function setMaxAccountMergingWindow(uint256 duration) external onlyOwner {
maxAccountMergingDuration = duration;
emit MaxAccountMergingDurationUpdated(duration);
}
function setMaxEscrowDuration(uint256 duration) external onlyOwner {
max_duration = duration;
emit MaxEscrowDurationUpdated(duration);
}
/* Nominate an account to merge escrow and vesting schedule */
function nominateAccountToMerge(address account) external {
require(account != msg.sender, "Cannot nominate own account to merge");
require(accountMergingIsOpen(), "Account merging has ended");
require(issuer().debtBalanceOf(msg.sender, "sUSD") == 0, "Cannot merge accounts with debt");
nominatedReceiver[msg.sender] = account;
emit NominateAccountToMerge(msg.sender, account);
}
function mergeAccount(address from, uint256[] calldata entryIDs) external {
require(accountMergingIsOpen(), "Account merging has ended");
require(issuer().debtBalanceOf(from, "sUSD") == 0, "Cannot merge accounts with debt");
require(nominatedReceiver[from] == msg.sender, "Address is not nominated to merge");
address to = msg.sender;
uint256 totalEscrowAmountMerged;
VestingEntries.VestingEntry memory entry;
for (uint i = 0; i < entryIDs.length; i++) {
// retrieve entry
entry = vestingSchedules(from, entryIDs[i]);
/* ignore vesting entries with zero escrowAmount */
if (entry.escrowAmount != 0) {
// set previous entry amount to zero
state().setZeroAmount(from, entryIDs[i]);
// append new entry for recipient, the new entry will have new entryID
state().addVestingEntry(to, entry);
/* Add the escrowAmount of entry to the totalEscrowAmountMerged */
totalEscrowAmountMerged = totalEscrowAmountMerged.add(entry.escrowAmount);
}
}
// remove from old account
state().updateEscrowAccountBalance(from, -SafeCast.toInt256(totalEscrowAmountMerged));
// add to recipient account
state().updateEscrowAccountBalance(to, SafeCast.toInt256(totalEscrowAmountMerged));
emit AccountMerged(from, to, totalEscrowAmountMerged, entryIDs, block.timestamp);
}
/* ========== MIGRATION OLD ESCROW ========== */
function migrateVestingSchedule(address) external {
_notImplemented();
}
function migrateAccountEscrowBalances(
address[] calldata,
uint256[] calldata,
uint256[] calldata
) external {
_notImplemented();
}
/* ========== L2 MIGRATION ========== */
function burnForMigration(address, uint[] calldata) external returns (uint256, VestingEntries.VestingEntry[] memory) {
_notImplemented();
}
function importVestingEntries(
address,
uint256,
VestingEntries.VestingEntry[] calldata
) external {
_notImplemented();
}
/* ========== MODIFIERS ========== */
modifier onlyFeePool() {
require(msg.sender == address(feePool()), "Only the FeePool can perform this action");
_;
}
modifier onlySynthetix() {
require(msg.sender == address(synthetixERC20()), "Only Synthetix");
_;
}
/* ========== EVENTS ========== */
event Vested(address indexed beneficiary, uint time, uint value);
event VestingEntryCreated(address indexed beneficiary, uint time, uint value, uint duration, uint entryID);
event MaxEscrowDurationUpdated(uint newDuration);
event MaxAccountMergingDurationUpdated(uint newDuration);
event AccountMergingDurationUpdated(uint newDuration);
event AccountMergingStarted(uint time, uint endTime);
event AccountMerged(
address indexed accountToMerge,
address destinationAddress,
uint escrowAmountMerged,
uint[] entryIDs,
uint time
);
event NominateAccountToMerge(address indexed account, address destination);
event Revoked(address indexed account, address indexed recipient, uint targetAmount, uint startIndex, uint endIndex);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
pragma experimental ABIEncoderV2;
// Inheritance
import "./IRewardEscrowV2Frozen.sol";
import "./Owned.sol";
import "./MixinResolver.sol";
import "./LimitedSetup.sol";
// Libraries
import "./SafeDecimalMath.sol";
// Internal references
import "./IERC20.sol";
import "./IFeePool.sol";
import "./ISynthetix.sol";
import "./IIssuer.sol";
// https://docs.synthetix.io/contracts/RewardEscrow
/// SIP-252: this is the source for the base of immutable V2 escrow (renamed with suffix Frozen).
/// These sources need to exist here and match on-chain frozen contracts for tests and reference.
/// The reason for the naming mess is that the immutable LiquidatorRewards expects a working
/// RewardEscrowV2 resolver entry for its getReward method, so the "new" (would be V3)
/// needs to be found at that entry for liq-rewards to function.
contract BaseRewardEscrowV2Frozen is Owned, IRewardEscrowV2Frozen, LimitedSetup(8 weeks), MixinResolver {
using SafeMath for uint;
using SafeDecimalMath for uint;
mapping(address => mapping(uint256 => VestingEntries.VestingEntry)) public vestingSchedules;
mapping(address => uint256[]) public accountVestingEntryIDs;
/*Counter for new vesting entry ids. */
uint256 public nextEntryId;
/* An account's total escrowed synthetix balance to save recomputing this for fee extraction purposes. */
mapping(address => uint256) public totalEscrowedAccountBalance;
/* An account's total vested reward synthetix. */
mapping(address => uint256) public totalVestedAccountBalance;
/* Mapping of nominated address to recieve account merging */
mapping(address => address) public nominatedReceiver;
/* The total remaining escrowed balance, for verifying the actual synthetix balance of this contract against. */
uint256 public totalEscrowedBalance;
/* Max escrow duration */
uint public max_duration = 2 * 52 weeks; // Default max 2 years duration
/* Max account merging duration */
uint public maxAccountMergingDuration = 4 weeks; // Default 4 weeks is max
/* ========== ACCOUNT MERGING CONFIGURATION ========== */
uint public accountMergingDuration = 1 weeks;
uint public accountMergingStartTime;
/* ========== ADDRESS RESOLVER CONFIGURATION ========== */
bytes32 private constant CONTRACT_SYNTHETIX = "Synthetix";
bytes32 private constant CONTRACT_ISSUER = "Issuer";
bytes32 private constant CONTRACT_FEEPOOL = "FeePool";
/* ========== CONSTRUCTOR ========== */
constructor(address _owner, address _resolver) public Owned(_owner) MixinResolver(_resolver) {
nextEntryId = 1;
}
/* ========== VIEWS ======================= */
function feePool() internal view returns (IFeePool) {
return IFeePool(requireAndGetAddress(CONTRACT_FEEPOOL));
}
function synthetix() internal view returns (ISynthetix) {
return ISynthetix(requireAndGetAddress(CONTRACT_SYNTHETIX));
}
function issuer() internal view returns (IIssuer) {
return IIssuer(requireAndGetAddress(CONTRACT_ISSUER));
}
function _notImplemented() internal pure {
revert("Cannot be run on this layer");
}
/* ========== VIEW FUNCTIONS ========== */
// Note: use public visibility so that it can be invoked in a subclass
function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
addresses = new bytes32[](3);
addresses[0] = CONTRACT_SYNTHETIX;
addresses[1] = CONTRACT_FEEPOOL;
addresses[2] = CONTRACT_ISSUER;
}
/**
* @notice A simple alias to totalEscrowedAccountBalance: provides ERC20 balance integration.
*/
function balanceOf(address account) public view returns (uint) {
return totalEscrowedAccountBalance[account];
}
/**
* @notice The number of vesting dates in an account's schedule.
*/
function numVestingEntries(address account) external view returns (uint) {
return accountVestingEntryIDs[account].length;
}
/**
* @notice Get a particular schedule entry for an account.
* @return The vesting entry object and rate per second emission.
*/
function getVestingEntry(address account, uint256 entryID) external view returns (uint64 endTime, uint256 escrowAmount) {
endTime = vestingSchedules[account][entryID].endTime;
escrowAmount = vestingSchedules[account][entryID].escrowAmount;
}
function getVestingSchedules(
address account,
uint256 index,
uint256 pageSize
) external view returns (VestingEntries.VestingEntryWithID[] memory) {
uint256 endIndex = index + pageSize;
// If index starts after the endIndex return no results
if (endIndex <= index) {
return new VestingEntries.VestingEntryWithID[](0);
}
// If the page extends past the end of the accountVestingEntryIDs, truncate it.
if (endIndex > accountVestingEntryIDs[account].length) {
endIndex = accountVestingEntryIDs[account].length;
}
uint256 n = endIndex - index;
VestingEntries.VestingEntryWithID[] memory vestingEntries = new VestingEntries.VestingEntryWithID[](n);
for (uint256 i; i < n; i++) {
uint256 entryID = accountVestingEntryIDs[account][i + index];
VestingEntries.VestingEntry memory entry = vestingSchedules[account][entryID];
vestingEntries[i] = VestingEntries.VestingEntryWithID({
endTime: uint64(entry.endTime),
escrowAmount: entry.escrowAmount,
entryID: entryID
});
}
return vestingEntries;
}
function getAccountVestingEntryIDs(
address account,
uint256 index,
uint256 pageSize
) external view returns (uint256[] memory) {
uint256 endIndex = index + pageSize;
// If the page extends past the end of the accountVestingEntryIDs, truncate it.
if (endIndex > accountVestingEntryIDs[account].length) {
endIndex = accountVestingEntryIDs[account].length;
}
if (endIndex <= index) {
return new uint256[](0);
}
uint256 n = endIndex - index;
uint256[] memory page = new uint256[](n);
for (uint256 i; i < n; i++) {
page[i] = accountVestingEntryIDs[account][i + index];
}
return page;
}
function getVestingQuantity(address account, uint256[] calldata entryIDs) external view returns (uint total) {
for (uint i = 0; i < entryIDs.length; i++) {
VestingEntries.VestingEntry memory entry = vestingSchedules[account][entryIDs[i]];
/* Skip entry if escrowAmount == 0 */
if (entry.escrowAmount != 0) {
uint256 quantity = _claimableAmount(entry);
/* add quantity to total */
total = total.add(quantity);
}
}
}
function getVestingEntryClaimable(address account, uint256 entryID) external view returns (uint) {
VestingEntries.VestingEntry memory entry = vestingSchedules[account][entryID];
return _claimableAmount(entry);
}
function _claimableAmount(VestingEntries.VestingEntry memory _entry) internal view returns (uint256) {
uint256 quantity;
if (_entry.escrowAmount != 0) {
/* Escrow amounts claimable if block.timestamp equal to or after entry endTime */
quantity = block.timestamp >= _entry.endTime ? _entry.escrowAmount : 0;
}
return quantity;
}
/* ========== MUTATIVE FUNCTIONS ========== */
/**
* Vest escrowed amounts that are claimable
* Allows users to vest their vesting entries based on msg.sender
*/
function vest(uint256[] calldata entryIDs) external {
uint256 total;
for (uint i = 0; i < entryIDs.length; i++) {
VestingEntries.VestingEntry storage entry = vestingSchedules[msg.sender][entryIDs[i]];
/* Skip entry if escrowAmount == 0 already vested */
if (entry.escrowAmount != 0) {
uint256 quantity = _claimableAmount(entry);
/* update entry to remove escrowAmount */
if (quantity > 0) {
entry.escrowAmount = 0;
}
/* add quantity to total */
total = total.add(quantity);
}
}
/* Transfer vested tokens. Will revert if total > totalEscrowedAccountBalance */
if (total != 0) {
_transferVestedTokens(msg.sender, total);
}
}
/**
* @notice Create an escrow entry to lock SNX for a given duration in seconds
* @dev This call expects that the depositor (msg.sender) has already approved the Reward escrow contract
to spend the the amount being escrowed.
*/
function createEscrowEntry(
address beneficiary,
uint256 deposit,
uint256 duration
) external {
require(beneficiary != address(0), "Cannot create escrow with address(0)");
/* Transfer SNX from msg.sender */
require(IERC20(address(synthetix())).transferFrom(msg.sender, address(this), deposit), "token transfer failed");
/* Append vesting entry for the beneficiary address */
_appendVestingEntry(beneficiary, deposit, duration);
}
/**
* @notice Add a new vesting entry at a given time and quantity to an account's schedule.
* @dev A call to this should accompany a previous successful call to synthetix.transfer(rewardEscrow, amount),
* to ensure that when the funds are withdrawn, there is enough balance.
* @param account The account to append a new vesting entry to.
* @param quantity The quantity of SNX that will be escrowed.
* @param duration The duration that SNX will be emitted.
*/
function appendVestingEntry(
address account,
uint256 quantity,
uint256 duration
) external onlyFeePool {
_appendVestingEntry(account, quantity, duration);
}
/* Transfer vested tokens and update totalEscrowedAccountBalance, totalVestedAccountBalance */
function _transferVestedTokens(address _account, uint256 _amount) internal {
_reduceAccountEscrowBalances(_account, _amount);
totalVestedAccountBalance[_account] = totalVestedAccountBalance[_account].add(_amount);
IERC20(address(synthetix())).transfer(_account, _amount);
emit Vested(_account, block.timestamp, _amount);
}
function _reduceAccountEscrowBalances(address _account, uint256 _amount) internal {
// Reverts if amount being vested is greater than the account's existing totalEscrowedAccountBalance
totalEscrowedBalance = totalEscrowedBalance.sub(_amount);
totalEscrowedAccountBalance[_account] = totalEscrowedAccountBalance[_account].sub(_amount);
}
/* ========== ACCOUNT MERGING ========== */
function accountMergingIsOpen() public view returns (bool) {
return accountMergingStartTime.add(accountMergingDuration) > block.timestamp;
}
function startMergingWindow() external onlyOwner {
accountMergingStartTime = block.timestamp;
emit AccountMergingStarted(accountMergingStartTime, accountMergingStartTime.add(accountMergingDuration));
}
function setAccountMergingDuration(uint256 duration) external onlyOwner {
require(duration <= maxAccountMergingDuration, "exceeds max merging duration");
accountMergingDuration = duration;
emit AccountMergingDurationUpdated(duration);
}
function setMaxAccountMergingWindow(uint256 duration) external onlyOwner {
maxAccountMergingDuration = duration;
emit MaxAccountMergingDurationUpdated(duration);
}
function setMaxEscrowDuration(uint256 duration) external onlyOwner {
max_duration = duration;
emit MaxEscrowDurationUpdated(duration);
}
/* Nominate an account to merge escrow and vesting schedule */
function nominateAccountToMerge(address account) external {
require(account != msg.sender, "Cannot nominate own account to merge");
require(accountMergingIsOpen(), "Account merging has ended");
require(issuer().debtBalanceOf(msg.sender, "sUSD") == 0, "Cannot merge accounts with debt");
nominatedReceiver[msg.sender] = account;
emit NominateAccountToMerge(msg.sender, account);
}
function mergeAccount(address accountToMerge, uint256[] calldata entryIDs) external {
require(accountMergingIsOpen(), "Account merging has ended");
require(issuer().debtBalanceOf(accountToMerge, "sUSD") == 0, "Cannot merge accounts with debt");
require(nominatedReceiver[accountToMerge] == msg.sender, "Address is not nominated to merge");
uint256 totalEscrowAmountMerged;
for (uint i = 0; i < entryIDs.length; i++) {
// retrieve entry
VestingEntries.VestingEntry memory entry = vestingSchedules[accountToMerge][entryIDs[i]];
/* ignore vesting entries with zero escrowAmount */
if (entry.escrowAmount != 0) {
/* copy entry to msg.sender (destination address) */
vestingSchedules[msg.sender][entryIDs[i]] = entry;
/* Add the escrowAmount of entry to the totalEscrowAmountMerged */
totalEscrowAmountMerged = totalEscrowAmountMerged.add(entry.escrowAmount);
/* append entryID to list of entries for account */
accountVestingEntryIDs[msg.sender].push(entryIDs[i]);
/* Delete entry from accountToMerge */
delete vestingSchedules[accountToMerge][entryIDs[i]];
}
}
/* update totalEscrowedAccountBalance for merged account and accountToMerge */
totalEscrowedAccountBalance[accountToMerge] = totalEscrowedAccountBalance[accountToMerge].sub(
totalEscrowAmountMerged
);
totalEscrowedAccountBalance[msg.sender] = totalEscrowedAccountBalance[msg.sender].add(totalEscrowAmountMerged);
emit AccountMerged(accountToMerge, msg.sender, totalEscrowAmountMerged, entryIDs, block.timestamp);
}
/* Internal function for importing vesting entry and creating new entry for escrow liquidations */
function _addVestingEntry(address account, VestingEntries.VestingEntry memory entry) internal returns (uint) {
uint entryID = nextEntryId;
vestingSchedules[account][entryID] = entry;
/* append entryID to list of entries for account */
accountVestingEntryIDs[account].push(entryID);
/* Increment the next entry id. */
nextEntryId = nextEntryId.add(1);
return entryID;
}
/* ========== MIGRATION OLD ESCROW ========== */
function migrateVestingSchedule(address) external {
_notImplemented();
}
function migrateAccountEscrowBalances(
address[] calldata,
uint256[] calldata,
uint256[] calldata
) external {
_notImplemented();
}
/* ========== L2 MIGRATION ========== */
function burnForMigration(address, uint[] calldata) external returns (uint256, VestingEntries.VestingEntry[] memory) {
_notImplemented();
}
function importVestingEntries(
address,
uint256,
VestingEntries.VestingEntry[] calldata
) external {
_notImplemented();
}
/* ========== INTERNALS ========== */
function _appendVestingEntry(
address account,
uint256 quantity,
uint256 duration
) internal {
/* No empty or already-passed vesting entries allowed. */
require(quantity != 0, "Quantity cannot be zero");
require(duration > 0 && duration <= max_duration, "Cannot escrow with 0 duration OR above max_duration");
/* There must be enough balance in the contract to provide for the vesting entry. */
totalEscrowedBalance = totalEscrowedBalance.add(quantity);
require(
totalEscrowedBalance <= IERC20(address(synthetix())).balanceOf(address(this)),
"Must be enough balance in the contract to provide for the vesting entry"
);
/* Escrow the tokens for duration. */
uint endTime = block.timestamp + duration;
/* Add quantity to account's escrowed balance */
totalEscrowedAccountBalance[account] = totalEscrowedAccountBalance[account].add(quantity);
uint entryID = nextEntryId;
vestingSchedules[account][entryID] = VestingEntries.VestingEntry({endTime: uint64(endTime), escrowAmount: quantity});
accountVestingEntryIDs[account].push(entryID);
/* Increment the next entry id. */
nextEntryId = nextEntryId.add(1);
emit VestingEntryCreated(account, block.timestamp, quantity, duration, entryID);
}
/* ========== MODIFIERS ========== */
modifier onlyFeePool() {
require(msg.sender == address(feePool()), "Only the FeePool can perform this action");
_;
}
/* ========== EVENTS ========== */
event Vested(address indexed beneficiary, uint time, uint value);
event VestingEntryCreated(address indexed beneficiary, uint time, uint value, uint duration, uint entryID);
event MaxEscrowDurationUpdated(uint newDuration);
event MaxAccountMergingDurationUpdated(uint newDuration);
event AccountMergingDurationUpdated(uint newDuration);
event AccountMergingStarted(uint time, uint endTime);
event AccountMerged(
address indexed accountToMerge,
address destinationAddress,
uint escrowAmountMerged,
uint[] entryIDs,
uint time
);
event NominateAccountToMerge(address indexed account, address destination);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
pragma experimental ABIEncoderV2;
// Inheritance
import "./IERC20.sol";
import "./ExternStateToken.sol";
import "./MixinResolver.sol";
import "./ISynthetix.sol";
// Internal references
import "./ISynth.sol";
import "./TokenState.sol";
import "./ISystemStatus.sol";
import "./IExchanger.sol";
import "./IIssuer.sol";
import "./IRewardsDistribution.sol";
import "./ILiquidator.sol";
import "./ILiquidatorRewards.sol";
import "./IVirtualSynth.sol";
import "./IRewardEscrowV2.sol";
contract BaseSynthetix is IERC20, ExternStateToken, MixinResolver, ISynthetix {
// ========== STATE VARIABLES ==========
// Available Synths which can be used with the system
string public constant TOKEN_NAME = "Synthetix Network Token";
string public constant TOKEN_SYMBOL = "SNX";
uint8 public constant DECIMALS = 18;
bytes32 public constant sUSD = "sUSD";
// ========== ADDRESS RESOLVER CONFIGURATION ==========
bytes32 private constant CONTRACT_SYSTEMSTATUS = "SystemStatus";
bytes32 private constant CONTRACT_EXCHANGER = "Exchanger";
bytes32 private constant CONTRACT_ISSUER = "Issuer";
bytes32 private constant CONTRACT_REWARDSDISTRIBUTION = "RewardsDistribution";
bytes32 private constant CONTRACT_LIQUIDATORREWARDS = "LiquidatorRewards";
bytes32 private constant CONTRACT_LIQUIDATOR = "Liquidator";
bytes32 private constant CONTRACT_REWARDESCROW_V2 = "RewardEscrowV2";
bytes32 private constant CONTRACT_V3_LEGACYMARKET = "LegacyMarket";
bytes32 private constant CONTRACT_DEBT_MIGRATOR_ON_ETHEREUM = "DebtMigratorOnEthereum";
// ========== CONSTRUCTOR ==========
constructor(
address payable _proxy,
TokenState _tokenState,
address _owner,
uint _totalSupply,
address _resolver
)
public
ExternStateToken(_proxy, _tokenState, TOKEN_NAME, TOKEN_SYMBOL, _totalSupply, DECIMALS, _owner)
MixinResolver(_resolver)
{}
// ========== VIEWS ==========
// Note: use public visibility so that it can be invoked in a subclass
function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
addresses = new bytes32[](7);
addresses[0] = CONTRACT_SYSTEMSTATUS;
addresses[1] = CONTRACT_EXCHANGER;
addresses[2] = CONTRACT_ISSUER;
addresses[3] = CONTRACT_REWARDSDISTRIBUTION;
addresses[4] = CONTRACT_LIQUIDATORREWARDS;
addresses[5] = CONTRACT_LIQUIDATOR;
addresses[6] = CONTRACT_REWARDESCROW_V2;
}
function systemStatus() internal view returns (ISystemStatus) {
return ISystemStatus(requireAndGetAddress(CONTRACT_SYSTEMSTATUS));
}
function exchanger() internal view returns (IExchanger) {
return IExchanger(requireAndGetAddress(CONTRACT_EXCHANGER));
}
function issuer() internal view returns (IIssuer) {
return IIssuer(requireAndGetAddress(CONTRACT_ISSUER));
}
function rewardsDistribution() internal view returns (IRewardsDistribution) {
return IRewardsDistribution(requireAndGetAddress(CONTRACT_REWARDSDISTRIBUTION));
}
function liquidatorRewards() internal view returns (ILiquidatorRewards) {
return ILiquidatorRewards(requireAndGetAddress(CONTRACT_LIQUIDATORREWARDS));
}
function rewardEscrowV2() internal view returns (IRewardEscrowV2) {
return IRewardEscrowV2(requireAndGetAddress(CONTRACT_REWARDESCROW_V2));
}
function liquidator() internal view returns (ILiquidator) {
return ILiquidator(requireAndGetAddress(CONTRACT_LIQUIDATOR));
}
function debtBalanceOf(address account, bytes32 currencyKey) external view returns (uint) {
return issuer().debtBalanceOf(account, currencyKey);
}
function totalIssuedSynths(bytes32 currencyKey) external view returns (uint) {
return issuer().totalIssuedSynths(currencyKey, false);
}
function totalIssuedSynthsExcludeOtherCollateral(bytes32 currencyKey) external view returns (uint) {
return issuer().totalIssuedSynths(currencyKey, true);
}
function availableCurrencyKeys() external view returns (bytes32[] memory) {
return issuer().availableCurrencyKeys();
}
function availableSynthCount() external view returns (uint) {
return issuer().availableSynthCount();
}
function availableSynths(uint index) external view returns (ISynth) {
return issuer().availableSynths(index);
}
function synths(bytes32 currencyKey) external view returns (ISynth) {
return issuer().synths(currencyKey);
}
function synthsByAddress(address synthAddress) external view returns (bytes32) {
return issuer().synthsByAddress(synthAddress);
}
function isWaitingPeriod(bytes32 currencyKey) external view returns (bool) {
return exchanger().maxSecsLeftInWaitingPeriod(messageSender, currencyKey) > 0;
}
function anySynthOrSNXRateIsInvalid() external view returns (bool anyRateInvalid) {
return issuer().anySynthOrSNXRateIsInvalid();
}
function maxIssuableSynths(address account) external view returns (uint maxIssuable) {
return issuer().maxIssuableSynths(account);
}
function remainingIssuableSynths(address account)
external
view
returns (
uint maxIssuable,
uint alreadyIssued,
uint totalSystemDebt
)
{
return issuer().remainingIssuableSynths(account);
}
function collateralisationRatio(address _issuer) external view returns (uint) {
return issuer().collateralisationRatio(_issuer);
}
function collateral(address account) external view returns (uint) {
return issuer().collateral(account);
}
function transferableSynthetix(address account) external view returns (uint transferable) {
(transferable, ) = issuer().transferableSynthetixAndAnyRateIsInvalid(account, tokenState.balanceOf(account));
}
/// the index of the first non zero RewardEscrowV2 entry for an account in order of iteration over accountVestingEntryIDs.
/// This is intended as a convenience off-chain view for liquidators to calculate the startIndex to pass
/// into liquidateDelinquentAccountEscrowIndex to save gas.
function getFirstNonZeroEscrowIndex(address account) external view returns (uint) {
uint numIds = rewardEscrowV2().numVestingEntries(account);
uint entryID;
VestingEntries.VestingEntry memory entry;
for (uint i = 0; i < numIds; i++) {
entryID = rewardEscrowV2().accountVestingEntryIDs(account, i);
entry = rewardEscrowV2().vestingSchedules(account, entryID);
if (entry.escrowAmount > 0) {
return i;
}
}
revert("all entries are zero");
}
function _canTransfer(address account, uint value) internal view returns (bool) {
// Always allow legacy market to transfer
// note if legacy market is not yet available this will just return 0 address and it will never be true
address legacyMarketAddress = resolver.getAddress(CONTRACT_V3_LEGACYMARKET);
if ((messageSender != address(0) && messageSender == legacyMarketAddress) || account == legacyMarketAddress) {
return true;
}
if (issuer().debtBalanceOf(account, sUSD) > 0) {
(uint transferable, bool anyRateIsInvalid) =
issuer().transferableSynthetixAndAnyRateIsInvalid(account, tokenState.balanceOf(account));
require(value <= transferable, "Cannot transfer staked or escrowed SNX");
require(!anyRateIsInvalid, "A synth or SNX rate is invalid");
}
return true;
}
// ========== MUTATIVE FUNCTIONS ==========
function exchange(
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey
) external exchangeActive(sourceCurrencyKey, destinationCurrencyKey) optionalProxy returns (uint amountReceived) {
(amountReceived, ) = exchanger().exchange(
messageSender,
messageSender,
sourceCurrencyKey,
sourceAmount,
destinationCurrencyKey,
messageSender,
false,
messageSender,
bytes32(0)
);
}
function exchangeOnBehalf(
address exchangeForAddress,
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey
) external exchangeActive(sourceCurrencyKey, destinationCurrencyKey) optionalProxy returns (uint amountReceived) {
(amountReceived, ) = exchanger().exchange(
exchangeForAddress,
messageSender,
sourceCurrencyKey,
sourceAmount,
destinationCurrencyKey,
exchangeForAddress,
false,
exchangeForAddress,
bytes32(0)
);
}
function settle(bytes32 currencyKey)
external
optionalProxy
returns (
uint reclaimed,
uint refunded,
uint numEntriesSettled
)
{
return exchanger().settle(messageSender, currencyKey);
}
function exchangeWithTracking(
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey,
address rewardAddress,
bytes32 trackingCode
) external exchangeActive(sourceCurrencyKey, destinationCurrencyKey) optionalProxy returns (uint amountReceived) {
(amountReceived, ) = exchanger().exchange(
messageSender,
messageSender,
sourceCurrencyKey,
sourceAmount,
destinationCurrencyKey,
messageSender,
false,
rewardAddress,
trackingCode
);
}
function exchangeOnBehalfWithTracking(
address exchangeForAddress,
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey,
address rewardAddress,
bytes32 trackingCode
) external exchangeActive(sourceCurrencyKey, destinationCurrencyKey) optionalProxy returns (uint amountReceived) {
(amountReceived, ) = exchanger().exchange(
exchangeForAddress,
messageSender,
sourceCurrencyKey,
sourceAmount,
destinationCurrencyKey,
exchangeForAddress,
false,
rewardAddress,
trackingCode
);
}
function transfer(address to, uint value) external onlyProxyOrInternal systemActive returns (bool) {
// Ensure they're not trying to exceed their locked amount -- only if they have debt.
_canTransfer(messageSender, value);
// Perform the transfer: if there is a problem an exception will be thrown in this call.
_transferByProxy(messageSender, to, value);
return true;
}
function transferFrom(
address from,
address to,
uint value
) external onlyProxyOrInternal systemActive returns (bool) {
// Ensure they're not trying to exceed their locked amount -- only if they have debt.
_canTransfer(from, value);
// Perform the transfer: if there is a problem,
// an exception will be thrown in this call.
return _transferFromByProxy(messageSender, from, to, value);
}
// SIP-252: migration of SNX token balance from old to new escrow rewards contract
function migrateEscrowContractBalance() external onlyOwner {
address from = resolver.requireAndGetAddress("RewardEscrowV2Frozen", "Old escrow address unset");
// technically the below could use `rewardEscrowV2()`, but in the case of a migration it's better to avoid
// using the cached value and read the most updated one directly from the resolver
address to = resolver.requireAndGetAddress("RewardEscrowV2", "New escrow address unset");
require(to != from, "cannot migrate to same address");
uint currentBalance = tokenState.balanceOf(from);
// allow no-op for idempotent migration steps in case action was performed already
if (currentBalance > 0) {
_internalTransfer(from, to, currentBalance);
}
}
function issueSynths(uint amount) external issuanceActive optionalProxy {
return issuer().issueSynths(messageSender, amount);
}
function issueSynthsOnBehalf(address issueForAddress, uint amount) external issuanceActive optionalProxy {
return issuer().issueSynthsOnBehalf(issueForAddress, messageSender, amount);
}
function issueMaxSynths() external issuanceActive optionalProxy {
return issuer().issueMaxSynths(messageSender);
}
function issueMaxSynthsOnBehalf(address issueForAddress) external issuanceActive optionalProxy {
return issuer().issueMaxSynthsOnBehalf(issueForAddress, messageSender);
}
function burnSynths(uint amount) external issuanceActive optionalProxy {
return issuer().burnSynths(messageSender, amount);
}
function burnSynthsOnBehalf(address burnForAddress, uint amount) external issuanceActive optionalProxy {
return issuer().burnSynthsOnBehalf(burnForAddress, messageSender, amount);
}
function burnSynthsToTarget() external issuanceActive optionalProxy {
return issuer().burnSynthsToTarget(messageSender);
}
function burnSynthsToTargetOnBehalf(address burnForAddress) external issuanceActive optionalProxy {
return issuer().burnSynthsToTargetOnBehalf(burnForAddress, messageSender);
}
/// @notice Force liquidate a delinquent account and distribute the redeemed SNX rewards amongst the appropriate recipients.
/// @dev The SNX transfers will revert if the amount to send is more than balanceOf account (i.e. due to escrowed balance).
function liquidateDelinquentAccount(address account) external systemActive optionalProxy returns (bool) {
return _liquidateDelinquentAccount(account, 0, messageSender);
}
/// @param escrowStartIndex: index into the account's vesting entries list to start iterating from
/// when liquidating from escrow in order to save gas (the default method uses 0 as default)
function liquidateDelinquentAccountEscrowIndex(address account, uint escrowStartIndex)
external
systemActive
optionalProxy
returns (bool)
{
return _liquidateDelinquentAccount(account, escrowStartIndex, messageSender);
}
/// @notice Force liquidate a delinquent account and distribute the redeemed SNX rewards amongst the appropriate recipients.
/// @dev The SNX transfers will revert if the amount to send is more than balanceOf account (i.e. due to escrowed balance).
function _liquidateDelinquentAccount(
address account,
uint escrowStartIndex,
address liquidatorAccount
) internal returns (bool) {
// ensure the user has no liquidation rewards (also counted towards collateral) outstanding
liquidatorRewards().getReward(account);
(uint totalRedeemed, uint debtToRemove, uint escrowToLiquidate) = issuer().liquidateAccount(account, false);
// This transfers the to-be-liquidated part of escrow to the account (!) as liquid SNX.
// It is transferred to the account instead of to the rewards because of the liquidator / flagger
// rewards that may need to be paid (so need to be transferrable, to avoid edge cases)
if (escrowToLiquidate > 0) {
rewardEscrowV2().revokeFrom(account, account, escrowToLiquidate, escrowStartIndex);
}
emitAccountLiquidated(account, totalRedeemed, debtToRemove, liquidatorAccount);
// First, pay out the flag and liquidate rewards.
uint flagReward = liquidator().flagReward();
uint liquidateReward = liquidator().liquidateReward();
// Transfer the flagReward to the account who flagged this account for liquidation.
address flagger = liquidator().getLiquidationCallerForAccount(account);
bool flagRewardTransferSucceeded = _transferByProxy(account, flagger, flagReward);
require(flagRewardTransferSucceeded, "Flag reward transfer did not succeed");
// Transfer the liquidateReward to liquidator (the account who invoked this liquidation).
bool liquidateRewardTransferSucceeded = _transferByProxy(account, liquidatorAccount, liquidateReward);
require(liquidateRewardTransferSucceeded, "Liquidate reward transfer did not succeed");
if (totalRedeemed > 0) {
// Send the remaining SNX to the LiquidatorRewards contract.
bool liquidatorRewardTransferSucceeded = _transferByProxy(account, address(liquidatorRewards()), totalRedeemed);
require(liquidatorRewardTransferSucceeded, "Transfer to LiquidatorRewards failed");
// Inform the LiquidatorRewards contract about the incoming SNX rewards.
liquidatorRewards().notifyRewardAmount(totalRedeemed);
}
return true;
}
/// @notice Allows an account to self-liquidate anytime its c-ratio is below the target issuance ratio.
function liquidateSelf() external systemActive optionalProxy returns (bool) {
// must store liquidated account address because below functions may attempt to transfer SNX which changes messageSender
address liquidatedAccount = messageSender;
// ensure the user has no liquidation rewards (also counted towards collateral) outstanding
liquidatorRewards().getReward(liquidatedAccount);
// Self liquidate the account (`isSelfLiquidation` flag must be set to `true`).
// escrowToLiquidate is unused because it cannot be used for self-liquidations
(uint totalRedeemed, uint debtRemoved, ) = issuer().liquidateAccount(liquidatedAccount, true);
require(debtRemoved > 0, "cannot self liquidate");
emitAccountLiquidated(liquidatedAccount, totalRedeemed, debtRemoved, liquidatedAccount);
// Transfer the redeemed SNX to the LiquidatorRewards contract.
// Reverts if amount to redeem is more than balanceOf account (i.e. due to escrowed balance).
bool success = _transferByProxy(liquidatedAccount, address(liquidatorRewards()), totalRedeemed);
require(success, "Transfer to LiquidatorRewards failed");
// Inform the LiquidatorRewards contract about the incoming SNX rewards.
liquidatorRewards().notifyRewardAmount(totalRedeemed);
return success;
}
/**
* @notice allows for migration from v2x to v3 when an account has pending escrow entries
*/
function revokeAllEscrow(address account) external systemActive {
address legacyMarketAddress = resolver.getAddress(CONTRACT_V3_LEGACYMARKET);
require(msg.sender == legacyMarketAddress, "Only LegacyMarket can revoke escrow");
rewardEscrowV2().revokeFrom(account, legacyMarketAddress, rewardEscrowV2().totalEscrowedAccountBalance(account), 0);
}
function migrateAccountBalances(address account)
external
systemActive
returns (uint totalEscrowRevoked, uint totalLiquidBalance)
{
address debtMigratorOnEthereum = resolver.getAddress(CONTRACT_DEBT_MIGRATOR_ON_ETHEREUM);
require(msg.sender == debtMigratorOnEthereum, "Only L1 DebtMigrator");
// get their liquid SNX balance and transfer it to the migrator contract
totalLiquidBalance = tokenState.balanceOf(account);
if (totalLiquidBalance > 0) {
bool succeeded = _transferByProxy(account, debtMigratorOnEthereum, totalLiquidBalance);
require(succeeded, "snx transfer failed");
}
// get their escrowed SNX balance and revoke it all
totalEscrowRevoked = rewardEscrowV2().totalEscrowedAccountBalance(account);
if (totalEscrowRevoked > 0) {
rewardEscrowV2().revokeFrom(account, debtMigratorOnEthereum, totalEscrowRevoked, 0);
}
}
function exchangeWithTrackingForInitiator(
bytes32,
uint,
bytes32,
address,
bytes32
) external returns (uint) {
_notImplemented();
}
function exchangeWithVirtual(
bytes32,
uint,
bytes32,
bytes32
) external returns (uint, IVirtualSynth) {
_notImplemented();
}
function exchangeAtomically(
bytes32,
uint,
bytes32,
bytes32,
uint
) external returns (uint) {
_notImplemented();
}
function mint() external returns (bool) {
_notImplemented();
}
function mintSecondary(address, uint) external {
_notImplemented();
}
function mintSecondaryRewards(uint) external {
_notImplemented();
}
function burnSecondary(address, uint) external {
_notImplemented();
}
function _notImplemented() internal pure {
revert("Cannot be run on this layer");
}
// ========== MODIFIERS ==========
modifier systemActive() {
_systemActive();
_;
}
function _systemActive() private view {
systemStatus().requireSystemActive();
}
modifier issuanceActive() {
_issuanceActive();
_;
}
function _issuanceActive() private view {
systemStatus().requireIssuanceActive();
}
modifier exchangeActive(bytes32 src, bytes32 dest) {
_exchangeActive(src, dest);
_;
}
function _exchangeActive(bytes32 src, bytes32 dest) private view {
systemStatus().requireExchangeBetweenSynthsAllowed(src, dest);
}
modifier onlyExchanger() {
_onlyExchanger();
_;
}
function _onlyExchanger() private view {
require(msg.sender == address(exchanger()), "Only Exchanger can invoke this");
}
modifier onlyProxyOrInternal {
_onlyProxyOrInternal();
_;
}
function _onlyProxyOrInternal() internal {
if (msg.sender == address(proxy)) {
// allow proxy through, messageSender should be already set correctly
return;
} else if (_isInternalTransferCaller(msg.sender)) {
// optionalProxy behaviour only for the internal legacy contracts
messageSender = msg.sender;
} else {
revert("Only the proxy can call");
}
}
/// some legacy internal contracts use transfer methods directly on implementation
/// which isn't supported due to SIP-238 for other callers
function _isInternalTransferCaller(address caller) internal view returns (bool) {
// These entries are not required or cached in order to allow them to not exist (==address(0))
// e.g. due to not being available on L2 or at some future point in time.
return
// ordered to reduce gas for more frequent calls, bridge first, vesting and migrating after, legacy last
caller == resolver.getAddress("SynthetixBridgeToOptimism") ||
caller == resolver.getAddress("RewardEscrowV2") ||
caller == resolver.getAddress("DebtMigratorOnOptimism") ||
// legacy contracts
caller == resolver.getAddress("RewardEscrow") ||
caller == resolver.getAddress("SynthetixEscrow") ||
caller == resolver.getAddress("Depot");
}
// ========== EVENTS ==========
event AccountLiquidated(address indexed account, uint snxRedeemed, uint amountLiquidated, address liquidator);
bytes32 internal constant ACCOUNTLIQUIDATED_SIG = keccak256("AccountLiquidated(address,uint256,uint256,address)");
function emitAccountLiquidated(
address account,
uint256 snxRedeemed,
uint256 amountLiquidated,
address liquidator
) internal {
proxy._emit(
abi.encode(snxRedeemed, amountLiquidated, liquidator),
2,
ACCOUNTLIQUIDATED_SIG,
addressToBytes32(account),
0,
0
);
}
event SynthExchange(
address indexed account,
bytes32 fromCurrencyKey,
uint256 fromAmount,
bytes32 toCurrencyKey,
uint256 toAmount,
address toAddress
);
bytes32 internal constant SYNTH_EXCHANGE_SIG =
keccak256("SynthExchange(address,bytes32,uint256,bytes32,uint256,address)");
function emitSynthExchange(
address account,
bytes32 fromCurrencyKey,
uint256 fromAmount,
bytes32 toCurrencyKey,
uint256 toAmount,
address toAddress
) external onlyExchanger {
proxy._emit(
abi.encode(fromCurrencyKey, fromAmount, toCurrencyKey, toAmount, toAddress),
2,
SYNTH_EXCHANGE_SIG,
addressToBytes32(account),
0,
0
);
}
event ExchangeTracking(bytes32 indexed trackingCode, bytes32 toCurrencyKey, uint256 toAmount, uint256 fee);
bytes32 internal constant EXCHANGE_TRACKING_SIG = keccak256("ExchangeTracking(bytes32,bytes32,uint256,uint256)");
function emitExchangeTracking(
bytes32 trackingCode,
bytes32 toCurrencyKey,
uint256 toAmount,
uint256 fee
) external onlyExchanger {
proxy._emit(abi.encode(toCurrencyKey, toAmount, fee), 2, EXCHANGE_TRACKING_SIG, trackingCode, 0, 0);
}
event ExchangeReclaim(address indexed account, bytes32 currencyKey, uint amount);
bytes32 internal constant EXCHANGERECLAIM_SIG = keccak256("ExchangeReclaim(address,bytes32,uint256)");
function emitExchangeReclaim(
address account,
bytes32 currencyKey,
uint256 amount
) external onlyExchanger {
proxy._emit(abi.encode(currencyKey, amount), 2, EXCHANGERECLAIM_SIG, addressToBytes32(account), 0, 0);
}
event ExchangeRebate(address indexed account, bytes32 currencyKey, uint amount);
bytes32 internal constant EXCHANGEREBATE_SIG = keccak256("ExchangeRebate(address,bytes32,uint256)");
function emitExchangeRebate(
address account,
bytes32 currencyKey,
uint256 amount
) external onlyExchanger {
proxy._emit(abi.encode(currencyKey, amount), 2, EXCHANGEREBATE_SIG, addressToBytes32(account), 0, 0);
}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
pragma experimental ABIEncoderV2;
// Inheritance
import "./Owned.sol";
import "./MixinResolver.sol";
import "./MixinSystemSettings.sol";
import "./IBaseSynthetixBridge.sol";
// Libraries
import "./Math.sol";
import "./SafeDecimalMath.sol";
// Internal references
import "./ISynthetix.sol";
import "./IRewardEscrowV2.sol";
import "./IIssuer.sol";
import "./IFeePool.sol";
import "./IExchangeRates.sol";
import "./ISystemStatus.sol";
import "./iAbs_BaseCrossDomainMessenger.sol";
contract BaseSynthetixBridge is Owned, MixinSystemSettings, IBaseSynthetixBridge {
using SafeMath for uint;
using SafeDecimalMath for uint;
/* ========== ADDRESS RESOLVER CONFIGURATION ========== */
bytes32 private constant CONTRACT_EXT_MESSENGER = "ext:Messenger";
bytes32 internal constant CONTRACT_SYNTHETIX = "Synthetix";
bytes32 private constant CONTRACT_REWARDESCROW = "RewardEscrowV2";
bytes32 private constant CONTRACT_ISSUER = "Issuer";
bytes32 private constant CONTRACT_FEEPOOL = "FeePool";
bytes32 private constant CONTRACT_FLEXIBLESTORAGE = "FlexibleStorage";
bytes32 private constant CONTRACT_EXCHANGERATES = "ExchangeRates";
bytes32 private constant CONTRACT_SYSTEM_STATUS = "SystemStatus";
// have to define this function like this here because contract name is required for FlexibleStorage
function CONTRACT_NAME() public pure returns (bytes32);
bool public initiationActive;
bytes32 private constant SYNTH_TRANSFER_NAMESPACE = "SynthTransfer";
bytes32 private constant SYNTH_TRANSFER_SENT = "Sent";
bytes32 private constant SYNTH_TRANSFER_RECV = "Recv";
// ========== CONSTRUCTOR ==========
constructor(address _owner, address _resolver) public Owned(_owner) MixinSystemSettings(_resolver) {
initiationActive = true;
}
// ========== INTERNALS ============
function messenger() internal view returns (iAbs_BaseCrossDomainMessenger) {
return iAbs_BaseCrossDomainMessenger(requireAndGetAddress(CONTRACT_EXT_MESSENGER));
}
function synthetix() internal view returns (ISynthetix) {
return ISynthetix(requireAndGetAddress(CONTRACT_SYNTHETIX));
}
function rewardEscrowV2() internal view returns (IRewardEscrowV2) {
return IRewardEscrowV2(requireAndGetAddress(CONTRACT_REWARDESCROW));
}
function issuer() internal view returns (IIssuer) {
return IIssuer(requireAndGetAddress(CONTRACT_ISSUER));
}
function feePool() internal view returns (IFeePool) {
return IFeePool(requireAndGetAddress(CONTRACT_FEEPOOL));
}
function flexibleStorage() internal view returns (IFlexibleStorage) {
return IFlexibleStorage(requireAndGetAddress(CONTRACT_FLEXIBLESTORAGE));
}
function exchangeRates() internal view returns (IExchangeRates) {
return IExchangeRates(requireAndGetAddress(CONTRACT_EXCHANGERATES));
}
function systemStatus() internal view returns (ISystemStatus) {
return ISystemStatus(requireAndGetAddress(CONTRACT_SYSTEM_STATUS));
}
function initiatingActive() internal view {
require(initiationActive, "Initiation deactivated");
}
function counterpart() internal view returns (address);
function onlyAllowFromCounterpart() internal view {
// ensure function only callable from the L2 bridge via messenger (aka relayer)
iAbs_BaseCrossDomainMessenger _messenger = messenger();
require(msg.sender == address(_messenger), "Only the relayer can call this");
require(_messenger.xDomainMessageSender() == counterpart(), "Only a counterpart bridge can invoke");
}
/* ========== VIEWS ========== */
function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
bytes32[] memory existingAddresses = MixinSystemSettings.resolverAddressesRequired();
bytes32[] memory newAddresses = new bytes32[](8);
newAddresses[0] = CONTRACT_EXT_MESSENGER;
newAddresses[1] = CONTRACT_SYNTHETIX;
newAddresses[2] = CONTRACT_REWARDESCROW;
newAddresses[3] = CONTRACT_ISSUER;
newAddresses[4] = CONTRACT_FEEPOOL;
newAddresses[5] = CONTRACT_FLEXIBLESTORAGE;
newAddresses[6] = CONTRACT_EXCHANGERATES;
newAddresses[7] = CONTRACT_SYSTEM_STATUS;
addresses = combineArrays(existingAddresses, newAddresses);
}
function synthTransferSent() external view returns (uint) {
return _sumTransferAmounts(SYNTH_TRANSFER_SENT);
}
function synthTransferReceived() external view returns (uint) {
return _sumTransferAmounts(SYNTH_TRANSFER_RECV);
}
// ========== MODIFIERS ============
modifier requireInitiationActive() {
initiatingActive();
_;
}
modifier onlyCounterpart() {
onlyAllowFromCounterpart();
_;
}
// ========= RESTRICTED FUNCTIONS ==============
function suspendInitiation() external onlyOwner {
require(initiationActive, "Initiation suspended");
initiationActive = false;
emit InitiationSuspended();
}
function resumeInitiation() external onlyOwner {
require(!initiationActive, "Initiation not suspended");
initiationActive = true;
emit InitiationResumed();
}
function initiateSynthTransfer(
bytes32 currencyKey,
address destination,
uint amount
) external requireInitiationActive {
require(destination != address(0), "Cannot send to zero address");
require(getCrossChainSynthTransferEnabled(currencyKey) > 0, "Synth not enabled for cross chain transfer");
systemStatus().requireSynthActive(currencyKey);
_incrementSynthsTransferCounter(SYNTH_TRANSFER_SENT, currencyKey, amount);
bool rateInvalid = issuer().burnSynthsWithoutDebt(currencyKey, msg.sender, amount);
require(!rateInvalid, "Cannot initiate if synth rate is invalid");
// create message payload
bytes memory messageData =
abi.encodeWithSelector(this.finalizeSynthTransfer.selector, currencyKey, destination, amount);
// relay the message to Bridge on L1 via L2 Messenger
messenger().sendMessage(
counterpart(),
messageData,
uint32(getCrossDomainMessageGasLimit(CrossDomainMessageGasLimits.Withdrawal))
);
emit InitiateSynthTransfer(currencyKey, destination, amount);
}
function finalizeSynthTransfer(
bytes32 currencyKey,
address destination,
uint amount
) external onlyCounterpart {
_incrementSynthsTransferCounter(SYNTH_TRANSFER_RECV, currencyKey, amount);
issuer().issueSynthsWithoutDebt(currencyKey, destination, amount);
emit FinalizeSynthTransfer(currencyKey, destination, amount);
}
// ==== INTERNAL FUNCTIONS ====
function _incrementSynthsTransferCounter(
bytes32 group,
bytes32 currencyKey,
uint amount
) internal {
bytes32 key = keccak256(abi.encodePacked(SYNTH_TRANSFER_NAMESPACE, group, currencyKey));
uint currentSynths = flexibleStorage().getUIntValue(CONTRACT_NAME(), key);
flexibleStorage().setUIntValue(CONTRACT_NAME(), key, currentSynths.add(amount));
}
function _sumTransferAmounts(bytes32 group) internal view returns (uint sum) {
// get list of synths from issuer
bytes32[] memory currencyKeys = issuer().availableCurrencyKeys();
// get all synth rates
(uint[] memory rates, bool isInvalid) = exchangeRates().ratesAndInvalidForCurrencies(currencyKeys);
require(!isInvalid, "Rates are invalid");
// get all values
bytes32[] memory transferAmountKeys = new bytes32[](currencyKeys.length);
for (uint i = 0; i < currencyKeys.length; i++) {
transferAmountKeys[i] = keccak256(abi.encodePacked(SYNTH_TRANSFER_NAMESPACE, group, currencyKeys[i]));
}
uint[] memory transferAmounts = flexibleStorage().getUIntValues(CONTRACT_NAME(), transferAmountKeys);
for (uint i = 0; i < currencyKeys.length; i++) {
sum = sum.add(transferAmounts[i].multiplyDecimalRound(rates[i]));
}
}
// ========== EVENTS ==========
event InitiationSuspended();
event InitiationResumed();
event InitiateSynthTransfer(bytes32 indexed currencyKey, address indexed destination, uint256 amount);
event FinalizeSynthTransfer(bytes32 indexed currencyKey, address indexed destination, uint256 amount);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
// https://docs.synthetix.io/contracts/source/libraries/bytes32setlib/
library Bytes32SetLib {
struct Bytes32Set {
bytes32[] elements;
mapping(bytes32 => uint) indices;
}
function contains(Bytes32Set storage set, bytes32 candidate) internal view returns (bool) {
if (set.elements.length == 0) {
return false;
}
uint index = set.indices[candidate];
return index != 0 || set.elements[0] == candidate;
}
function getPage(
Bytes32Set storage set,
uint index,
uint pageSize
) internal view returns (bytes32[] memory) {
// NOTE: This implementation should be converted to slice operators if the compiler is updated to v0.6.0+
uint endIndex = index + pageSize; // The check below that endIndex <= index handles overflow.
// If the page extends past the end of the list, truncate it.
if (endIndex > set.elements.length) {
endIndex = set.elements.length;
}
if (endIndex <= index) {
return new bytes32[](0);
}
uint n = endIndex - index; // We already checked for negative overflow.
bytes32[] memory page = new bytes32[](n);
for (uint i; i < n; i++) {
page[i] = set.elements[i + index];
}
return page;
}
function add(Bytes32Set storage set, bytes32 element) internal {
// Adding to a set is an idempotent operation.
if (!contains(set, element)) {
set.indices[element] = set.elements.length;
set.elements.push(element);
}
}
function remove(Bytes32Set storage set, bytes32 element) internal {
require(contains(set, element), "Element not in set.");
// Replace the removed element with the last element of the list.
uint index = set.indices[element];
uint lastIndex = set.elements.length - 1; // We required that element is in the list, so it is not empty.
if (index != lastIndex) {
// No need to shift the last element if it is the one we want to delete.
bytes32 shiftedElement = set.elements[lastIndex];
set.elements[index] = shiftedElement;
set.indices[shiftedElement] = index;
}
set.elements.pop();
delete set.indices[element];
}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
// Inheritance
import "./Owned.sol";
import "./MixinResolver.sol";
import "./MixinSystemSettings.sol";
import "./ICircuitBreaker.sol";
// Libraries
import "./SafeDecimalMath.sol";
// Internal references
import "./ISynth.sol";
import "./ISystemStatus.sol";
import "./IExchangeRates.sol";
import "./Proxyable.sol";
// Chainlink
import "./AggregatorV2V3Interface.sol";
/**
* Compares current exchange rate to previous, and suspends a synth if the
* difference is outside of deviation bounds.
* Stores last "good" rate for each synth on each invocation.
* Inteded use is to use in combination with ExchangeRates on mutative exchange-like
* methods.
* Suspend functionality is public, resume functionality is controlled by owner.
*
* https://docs.synthetix.io/contracts/source/contracts/CircuitBreaker
*/
contract CircuitBreaker is Owned, MixinSystemSettings, ICircuitBreaker {
using SafeMath for uint;
using SafeDecimalMath for uint;
bytes32 public constant CONTRACT_NAME = "CircuitBreaker";
// is internal to have lastValue getter in interface in solidity v0.5
// TODO: after upgrading solidity, switch to just public lastValue instead
// of maintaining this internal one
mapping(address => uint) internal _lastValue;
mapping(address => bool) internal _circuitBroken;
/* ========== ADDRESS RESOLVER CONFIGURATION ========== */
bytes32 private constant CONTRACT_SYSTEMSTATUS = "SystemStatus";
bytes32 private constant CONTRACT_ISSUER = "Issuer";
bytes32 private constant CONTRACT_EXRATES = "ExchangeRates";
constructor(address _owner, address _resolver) public Owned(_owner) MixinSystemSettings(_resolver) {}
/* ========== VIEWS ========== */
function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
bytes32[] memory existingAddresses = MixinSystemSettings.resolverAddressesRequired();
bytes32[] memory newAddresses = new bytes32[](3);
newAddresses[0] = CONTRACT_SYSTEMSTATUS;
newAddresses[1] = CONTRACT_ISSUER;
newAddresses[2] = CONTRACT_EXRATES;
addresses = combineArrays(existingAddresses, newAddresses);
}
// Returns whether or not a rate would be come invalid
// ignores systemStatus check
function isInvalid(address oracleAddress, uint value) external view returns (bool) {
return _circuitBroken[oracleAddress] || _isRateOutOfBounds(oracleAddress, value) || value == 0;
}
function isDeviationAboveThreshold(uint base, uint comparison) external view returns (bool) {
return _isDeviationAboveThreshold(base, comparison);
}
function priceDeviationThresholdFactor() external view returns (uint) {
return getPriceDeviationThresholdFactor();
}
function lastValue(address oracleAddress) external view returns (uint) {
return _lastValue[oracleAddress];
}
function circuitBroken(address oracleAddress) external view returns (bool) {
return _circuitBroken[oracleAddress];
}
/* ========== Internal views ========== */
function systemStatus() internal view returns (ISystemStatus) {
return ISystemStatus(requireAndGetAddress(CONTRACT_SYSTEMSTATUS));
}
/* ========== Mutating ========== */
/**
* Checks rate deviation from previous and its "invalid" oracle state (stale rate, of flagged by oracle).
* If its valid, set the `circuitBoken` flag and return false. Continue storing price updates as normal.
* Also, checks that system is not suspended currently, if it is - doesn't perform any checks, and
* returns last rate and the current broken state, to prevent synths suspensions during maintenance.
*/
function probeCircuitBreaker(address oracleAddress, uint value) external onlyProbers returns (bool circuitBroken) {
require(oracleAddress != address(0), "Oracle address is 0");
// these conditional statements are ordered for short circuit (heh) efficiency to reduce gas usage
// in the usual case of no circuit broken.
if (
// cases where the new price should be triggering a circuit break
(value == 0 || _isRateOutOfBounds(oracleAddress, value)) &&
// other necessary states in order to break
!_circuitBroken[oracleAddress] &&
!systemStatus().systemSuspended()
) {
_circuitBroken[oracleAddress] = true;
emit CircuitBroken(oracleAddress, _lastValue[oracleAddress], value);
}
_lastValue[oracleAddress] = value;
return _circuitBroken[oracleAddress];
}
/**
* SIP-139
* resets the stored value for _lastValue for multiple currencies to the latest rate
* can be used to enable synths after a broken circuit happenned
* doesn't check deviations here, so believes that owner knows better
* emits LastRateOverridden
*/
function resetLastValue(address[] calldata oracleAddresses, uint[] calldata values) external onlyOwner {
for (uint i = 0; i < oracleAddresses.length; i++) {
require(oracleAddresses[i] != address(0), "Oracle address is 0");
emit LastValueOverridden(oracleAddresses[i], _lastValue[oracleAddresses[i]], values[i]);
_lastValue[oracleAddresses[i]] = values[i];
_circuitBroken[oracleAddresses[i]] = false;
}
}
/* ========== INTERNAL FUNCTIONS ========== */
function _isDeviationAboveThreshold(uint base, uint comparison) internal view returns (bool) {
if (base == 0 || comparison == 0) {
return true;
}
uint factor;
if (comparison > base) {
factor = comparison.divideDecimal(base);
} else {
factor = base.divideDecimal(comparison);
}
return factor >= getPriceDeviationThresholdFactor();
}
/**
* Rate is invalid if it is outside of deviation bounds relative to previous non-zero rate
*/
function _isRateOutOfBounds(address oracleAddress, uint current) internal view returns (bool) {
uint last = _lastValue[oracleAddress];
// `last == 0` indicates unset/unpopulated oracle. If we dont have any data on the previous oracle price,
// we should skip the deviation check and allow it to be populated.
if (last > 0) {
return _isDeviationAboveThreshold(last, current);
}
return false;
}
// ========== MODIFIERS =======
modifier onlyProbers() {
require(
msg.sender == requireAndGetAddress(CONTRACT_ISSUER) || msg.sender == requireAndGetAddress(CONTRACT_EXRATES),
"Only internal contracts can call this function"
);
_;
}
// ========== EVENTS ==========
// @notice signals that a the "last value" was overridden by one of the admin methods
// with a value that didn't come directly from the ExchangeRates.getRates methods
event LastValueOverridden(address indexed oracleAddress, uint256 previousValue, uint256 newValue);
// @notice signals that the circuit was broken
event CircuitBroken(address indexed oracleAddress, uint256 previousValue, uint256 newValue);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
pragma experimental ABIEncoderV2;
import "./SafeERC20.sol";
// Inheritance
import "./Owned.sol";
import "./MixinSystemSettings.sol";
import "./ICollateralLoan.sol";
// Libraries
import "./SafeDecimalMath.sol";
// Internal references
import "./ICollateralUtil.sol";
import "./ICollateralManager.sol";
import "./ISystemStatus.sol";
import "./IFeePool.sol";
import "./IIssuer.sol";
import "./ISynth.sol";
import "./IExchangeRates.sol";
import "./IExchanger.sol";
import "./IShortingRewards.sol";
contract Collateral is ICollateralLoan, Owned, MixinSystemSettings {
/* ========== LIBRARIES ========== */
using SafeMath for uint;
using SafeDecimalMath for uint;
using SafeERC20 for IERC20;
/* ========== CONSTANTS ========== */
bytes32 internal constant sUSD = "sUSD";
// ========== STATE VARIABLES ==========
// The synth corresponding to the collateral.
bytes32 public collateralKey;
// Stores open loans.
mapping(uint => Loan) public loans;
ICollateralManager public manager;
// The synths that this contract can issue.
bytes32[] public synths;
// Map from currency key to synth contract name.
mapping(bytes32 => bytes32) public synthsByKey;
// Map from currency key to the shorting rewards contract
mapping(bytes32 => address) public shortingRewards;
// ========== SETTER STATE VARIABLES ==========
// The minimum collateral ratio required to avoid liquidation.
uint public minCratio;
// The minimum amount of collateral to create a loan.
uint public minCollateral;
// The fee charged for issuing a loan.
uint public issueFeeRate;
bool public canOpenLoans = true;
/* ========== ADDRESS RESOLVER CONFIGURATION ========== */
bytes32 private constant CONTRACT_SYSTEMSTATUS = "SystemStatus";
bytes32 private constant CONTRACT_EXRATES = "ExchangeRates";
bytes32 private constant CONTRACT_EXCHANGER = "Exchanger";
bytes32 private constant CONTRACT_FEEPOOL = "FeePool";
bytes32 private constant CONTRACT_SYNTHSUSD = "SynthsUSD";
bytes32 private constant CONTRACT_COLLATERALUTIL = "CollateralUtil";
/* ========== CONSTRUCTOR ========== */
constructor(
address _owner,
ICollateralManager _manager,
address _resolver,
bytes32 _collateralKey,
uint _minCratio,
uint _minCollateral
) public Owned(_owner) MixinSystemSettings(_resolver) {
manager = _manager;
collateralKey = _collateralKey;
minCratio = _minCratio;
minCollateral = _minCollateral;
}
/* ========== VIEWS ========== */
function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
bytes32[] memory existingAddresses = MixinSystemSettings.resolverAddressesRequired();
bytes32[] memory newAddresses = new bytes32[](6);
newAddresses[0] = CONTRACT_FEEPOOL;
newAddresses[1] = CONTRACT_EXRATES;
newAddresses[2] = CONTRACT_EXCHANGER;
newAddresses[3] = CONTRACT_SYSTEMSTATUS;
newAddresses[4] = CONTRACT_SYNTHSUSD;
newAddresses[5] = CONTRACT_COLLATERALUTIL;
bytes32[] memory combined = combineArrays(existingAddresses, newAddresses);
addresses = combineArrays(combined, synths);
}
/* ---------- Related Contracts ---------- */
function _systemStatus() internal view returns (ISystemStatus) {
return ISystemStatus(requireAndGetAddress(CONTRACT_SYSTEMSTATUS));
}
function _synth(bytes32 synthName) internal view returns (ISynth) {
return ISynth(requireAndGetAddress(synthName));
}
function _synthsUSD() internal view returns (ISynth) {
return ISynth(requireAndGetAddress(CONTRACT_SYNTHSUSD));
}
function _exchangeRates() internal view returns (IExchangeRates) {
return IExchangeRates(requireAndGetAddress(CONTRACT_EXRATES));
}
function _exchanger() internal view returns (IExchanger) {
return IExchanger(requireAndGetAddress(CONTRACT_EXCHANGER));
}
function _feePool() internal view returns (IFeePool) {
return IFeePool(requireAndGetAddress(CONTRACT_FEEPOOL));
}
function _collateralUtil() internal view returns (ICollateralUtil) {
return ICollateralUtil(requireAndGetAddress(CONTRACT_COLLATERALUTIL));
}
/* ---------- Public Views ---------- */
function collateralRatio(uint id) public view returns (uint cratio) {
Loan memory loan = loans[id];
return _collateralUtil().getCollateralRatio(loan, collateralKey);
}
function liquidationAmount(uint id) public view returns (uint liqAmount) {
Loan memory loan = loans[id];
return _collateralUtil().liquidationAmount(loan, minCratio, collateralKey);
}
// The maximum number of synths issuable for this amount of collateral
function maxLoan(uint amount, bytes32 currency) public view returns (uint max) {
return _collateralUtil().maxLoan(amount, currency, minCratio, collateralKey);
}
function areSynthsAndCurrenciesSet(bytes32[] calldata _synthNamesInResolver, bytes32[] calldata _synthKeys)
external
view
returns (bool)
{
if (synths.length != _synthNamesInResolver.length) {
return false;
}
for (uint i = 0; i < _synthNamesInResolver.length; i++) {
bytes32 synthName = _synthNamesInResolver[i];
if (synths[i] != synthName) {
return false;
}
if (synthsByKey[_synthKeys[i]] != synths[i]) {
return false;
}
}
return true;
}
/* ---------- SETTERS ---------- */
function setMinCollateral(uint _minCollateral) external onlyOwner {
minCollateral = _minCollateral;
emit MinCollateralUpdated(minCollateral);
}
function setIssueFeeRate(uint _issueFeeRate) external onlyOwner {
issueFeeRate = _issueFeeRate;
emit IssueFeeRateUpdated(issueFeeRate);
}
function setCanOpenLoans(bool _canOpenLoans) external onlyOwner {
canOpenLoans = _canOpenLoans;
emit CanOpenLoansUpdated(canOpenLoans);
}
/* ---------- UTILITIES ---------- */
// Check the account has enough of the synth to make the payment
function _checkSynthBalance(
address payer,
bytes32 key,
uint amount
) internal view {
require(IERC20(address(_synth(synthsByKey[key]))).balanceOf(payer) >= amount, "Not enough balance");
}
// We set the interest index to 0 to indicate the loan has been closed.
function _checkLoanAvailable(Loan memory loan) internal view {
_isLoanOpen(loan.interestIndex);
require(loan.lastInteraction.add(getInteractionDelay(address(this))) <= block.timestamp, "Recently interacted");
}
function _isLoanOpen(uint interestIndex) internal pure {
require(interestIndex != 0, "Loan is closed");
}
/* ========== MUTATIVE FUNCTIONS ========== */
/* ---------- Synths ---------- */
function addSynths(bytes32[] calldata _synthNamesInResolver, bytes32[] calldata _synthKeys) external onlyOwner {
require(_synthNamesInResolver.length == _synthKeys.length, "Array length mismatch");
for (uint i = 0; i < _synthNamesInResolver.length; i++) {
bytes32 synthName = _synthNamesInResolver[i];
synths.push(synthName);
synthsByKey[_synthKeys[i]] = synthName;
}
// ensure cache has the latest
rebuildCache();
}
/* ---------- Rewards Contracts ---------- */
function addRewardsContracts(address rewardsContract, bytes32 synth) external onlyOwner {
shortingRewards[synth] = rewardsContract;
}
/* ---------- LOAN INTERACTIONS ---------- */
function _open(
uint collateral,
uint amount,
bytes32 currency,
bool short
) internal rateIsValid issuanceIsActive returns (uint id) {
// 0. Check if able to open loans.
require(canOpenLoans, "Open disabled");
// 1. We can only issue certain synths.
require(synthsByKey[currency] > 0, "Not allowed to issue");
// 2. Make sure the synth rate is not invalid.
require(!_exchangeRates().rateIsInvalid(currency), "Invalid rate");
// 3. Collateral >= minimum collateral size.
require(collateral >= minCollateral, "Not enough collateral");
// 4. Check we haven't hit the debt cap for non snx collateral.
(bool canIssue, bool anyRateIsInvalid) = manager.exceedsDebtLimit(amount, currency);
// 5. Check if we've hit the debt cap or any rate is invalid.
require(canIssue && !anyRateIsInvalid, "Debt limit or invalid rate");
// 6. Require requested loan < max loan.
require(amount <= maxLoan(collateral, currency), "Exceed max borrow power");
// 7. This fee is denominated in the currency of the loan.
uint issueFee = amount.multiplyDecimalRound(issueFeeRate);
// 8. Calculate the minting fee and subtract it from the loan amount.
uint loanAmountMinusFee = amount.sub(issueFee);
// 9. Get a Loan ID.
id = manager.getNewLoanId();
// 10. Create the loan struct.
loans[id] = Loan({
id: id,
account: msg.sender,
collateral: collateral,
currency: currency,
amount: amount,
short: short,
accruedInterest: 0,
interestIndex: 0,
lastInteraction: block.timestamp
});
// 11. Accrue interest on the loan.
_accrueInterest(loans[id]);
// 12. Pay the minting fees to the fee pool.
_payFees(issueFee, currency);
// 13. If its short, convert back to sUSD, otherwise issue the loan.
if (short) {
_synthsUSD().issue(msg.sender, _exchangeRates().effectiveValue(currency, loanAmountMinusFee, sUSD));
manager.incrementShorts(currency, amount);
if (shortingRewards[currency] != address(0)) {
IShortingRewards(shortingRewards[currency]).enrol(msg.sender, amount);
}
} else {
_synth(synthsByKey[currency]).issue(msg.sender, loanAmountMinusFee);
manager.incrementLongs(currency, amount);
}
// 14. Emit event for the newly opened loan.
emit LoanCreated(msg.sender, id, amount, collateral, currency, issueFee);
}
function _close(address borrower, uint id) internal rateIsValid issuanceIsActive returns (uint amount, uint collateral) {
// 0. Get the loan and accrue interest.
Loan storage loan = _getLoanAndAccrueInterest(id, borrower);
// 1. Check loan is open and last interaction time.
_checkLoanAvailable(loan);
// 2. Record loan as closed.
(amount, collateral) = _closeLoan(borrower, borrower, loan);
// 3. Emit the event for the closed loan.
emit LoanClosed(borrower, id);
}
function _closeByLiquidation(
address borrower,
address liquidator,
Loan storage loan
) internal returns (uint amount, uint collateral) {
(amount, collateral) = _closeLoan(borrower, liquidator, loan);
// Emit the event for the loan closed by liquidation.
emit LoanClosedByLiquidation(borrower, loan.id, liquidator, amount, collateral);
}
function _closeLoan(
address borrower,
address liquidator,
Loan storage loan
) internal returns (uint amount, uint collateral) {
// 0. Work out the total amount owing on the loan.
uint total = loan.amount.add(loan.accruedInterest);
// 1. Store this for the event.
amount = loan.amount;
// 2. Return collateral to the child class so it knows how much to transfer.
collateral = loan.collateral;
// 3. Check that the liquidator has enough synths.
_checkSynthBalance(liquidator, loan.currency, total);
// 4. Burn the synths.
_synth(synthsByKey[loan.currency]).burn(liquidator, total);
// 5. Tell the manager.
if (loan.short) {
manager.decrementShorts(loan.currency, loan.amount);
if (shortingRewards[loan.currency] != address(0)) {
IShortingRewards(shortingRewards[loan.currency]).withdraw(borrower, loan.amount);
}
} else {
manager.decrementLongs(loan.currency, loan.amount);
}
// 6. Pay fees.
_payFees(loan.accruedInterest, loan.currency);
// 7. Record loan as closed.
_recordLoanAsClosed(loan);
}
function _deposit(
address account,
uint id,
uint amount
) internal rateIsValid issuanceIsActive returns (uint, uint) {
// 0. They sent some value > 0
require(amount > 0, "Deposit must be above 0");
// 1. Get the loan.
// Owner is not important here, as it is a donation to the collateral of the loan
Loan storage loan = loans[id];
// 2. Check loan hasn't been closed or liquidated.
_isLoanOpen(loan.interestIndex);
// 3. Accrue interest on the loan.
_accrueInterest(loan);
// 4. Add the collateral.
loan.collateral = loan.collateral.add(amount);
// 5. Emit the event for the deposited collateral.
emit CollateralDeposited(account, id, amount, loan.collateral);
return (loan.amount, loan.collateral);
}
function _withdraw(uint id, uint amount) internal rateIsValid issuanceIsActive returns (uint, uint) {
// 0. Get the loan and accrue interest.
Loan storage loan = _getLoanAndAccrueInterest(id, msg.sender);
// 1. Subtract the collateral.
loan.collateral = loan.collateral.sub(amount);
// 2. Check that the new amount does not put them under the minimum c ratio.
_checkLoanRatio(loan);
// 3. Emit the event for the withdrawn collateral.
emit CollateralWithdrawn(msg.sender, id, amount, loan.collateral);
return (loan.amount, loan.collateral);
}
function _liquidate(
address borrower,
uint id,
uint payment
) internal rateIsValid issuanceIsActive returns (uint collateralLiquidated) {
require(payment > 0, "Payment must be above 0");
// 0. Get the loan and accrue interest.
Loan storage loan = _getLoanAndAccrueInterest(id, borrower);
// 1. Check they have enough balance to make the payment.
_checkSynthBalance(msg.sender, loan.currency, payment);
// 2. Check they are eligible for liquidation.
// Note: this will revert if collateral is 0, however that should only be possible if the loan amount is 0.
require(_collateralUtil().getCollateralRatio(loan, collateralKey) < minCratio, "Cratio above liq ratio");
// 3. Determine how much needs to be liquidated to fix their c ratio.
uint liqAmount = _collateralUtil().liquidationAmount(loan, minCratio, collateralKey);
// 4. Only allow them to liquidate enough to fix the c ratio.
uint amountToLiquidate = liqAmount < payment ? liqAmount : payment;
// 5. Work out the total amount owing on the loan.
uint amountOwing = loan.amount.add(loan.accruedInterest);
// 6. If its greater than the amount owing, we need to close the loan.
if (amountToLiquidate >= amountOwing) {
(, collateralLiquidated) = _closeByLiquidation(borrower, msg.sender, loan);
return collateralLiquidated;
}
// 7. Check they have enough balance to liquidate the loan.
_checkSynthBalance(msg.sender, loan.currency, amountToLiquidate);
// 8. Process the payment to workout interest/principal split.
_processPayment(loan, amountToLiquidate);
// 9. Work out how much collateral to redeem.
collateralLiquidated = _collateralUtil().collateralRedeemed(loan.currency, amountToLiquidate, collateralKey);
loan.collateral = loan.collateral.sub(collateralLiquidated);
// 10. Burn the synths from the liquidator.
_synth(synthsByKey[loan.currency]).burn(msg.sender, amountToLiquidate);
// 11. Emit the event for the partial liquidation.
emit LoanPartiallyLiquidated(borrower, id, msg.sender, amountToLiquidate, collateralLiquidated);
}
function _repay(
address borrower,
address repayer,
uint id,
uint payment
) internal rateIsValid issuanceIsActive returns (uint, uint) {
// 0. Get the loan.
// Owner is not important here, as it is a donation to repay the loan.
Loan storage loan = loans[id];
// 1. Check loan is open and last interaction time.
_checkLoanAvailable(loan);
// 2. Check the spender has enough synths to make the repayment
_checkSynthBalance(repayer, loan.currency, payment);
// 3. Accrue interest on the loan.
_accrueInterest(loan);
// 4. Process the payment.
_processPayment(loan, payment);
// 5. Burn synths from the payer
_synth(synthsByKey[loan.currency]).burn(repayer, payment);
// 6. Update the last interaction time.
loan.lastInteraction = block.timestamp;
// 7. Emit the event the repayment.
emit LoanRepaymentMade(borrower, repayer, id, payment, loan.amount);
// 8. Return the loan amount and collateral after repaying.
return (loan.amount, loan.collateral);
}
function _draw(uint id, uint amount) internal rateIsValid issuanceIsActive returns (uint, uint) {
// 0. Get the loan and accrue interest.
Loan storage loan = _getLoanAndAccrueInterest(id, msg.sender);
// 1. Check last interaction time.
_checkLoanAvailable(loan);
// 2. Add the requested amount.
loan.amount = loan.amount.add(amount);
// 3. If it is below the minimum, don't allow this draw.
_checkLoanRatio(loan);
// 4. This fee is denominated in the currency of the loan
uint issueFee = amount.multiplyDecimalRound(issueFeeRate);
// 5. Calculate the minting fee and subtract it from the draw amount
uint amountMinusFee = amount.sub(issueFee);
// 6. If its short, issue the synths.
if (loan.short) {
manager.incrementShorts(loan.currency, amount);
_synthsUSD().issue(msg.sender, _exchangeRates().effectiveValue(loan.currency, amountMinusFee, sUSD));
if (shortingRewards[loan.currency] != address(0)) {
IShortingRewards(shortingRewards[loan.currency]).enrol(msg.sender, amount);
}
} else {
manager.incrementLongs(loan.currency, amount);
_synth(synthsByKey[loan.currency]).issue(msg.sender, amountMinusFee);
}
// 7. Pay the minting fees to the fee pool
_payFees(issueFee, loan.currency);
// 8. Update the last interaction time.
loan.lastInteraction = block.timestamp;
// 9. Emit the event for the draw down.
emit LoanDrawnDown(msg.sender, id, amount);
return (loan.amount, loan.collateral);
}
// Update the cumulative interest rate for the currency that was interacted with.
function _accrueInterest(Loan storage loan) internal {
(uint differential, uint newIndex) = manager.accrueInterest(loan.interestIndex, loan.currency, loan.short);
// If the loan was just opened, don't record any interest. Otherwise multiply by the amount outstanding.
uint interest = loan.interestIndex == 0 ? 0 : loan.amount.multiplyDecimal(differential);
// Update the loan.
loan.accruedInterest = loan.accruedInterest.add(interest);
loan.interestIndex = newIndex;
}
// Works out the amount of interest and principal after a repayment is made.
function _processPayment(Loan storage loan, uint payment) internal {
require(payment > 0, "Payment must be above 0");
if (loan.accruedInterest > 0) {
uint interestPaid = payment > loan.accruedInterest ? loan.accruedInterest : payment;
loan.accruedInterest = loan.accruedInterest.sub(interestPaid);
payment = payment.sub(interestPaid);
_payFees(interestPaid, loan.currency);
}
// If there is more payment left after the interest, pay down the principal.
if (payment > 0) {
loan.amount = loan.amount.sub(payment);
// And get the manager to reduce the total long/short balance.
if (loan.short) {
manager.decrementShorts(loan.currency, payment);
if (shortingRewards[loan.currency] != address(0)) {
IShortingRewards(shortingRewards[loan.currency]).withdraw(loan.account, payment);
}
} else {
manager.decrementLongs(loan.currency, payment);
}
}
}
// Take an amount of fees in a certain synth and convert it to sUSD before paying the fee pool.
function _payFees(uint amount, bytes32 synth) internal {
if (amount > 0) {
if (synth != sUSD) {
amount = _exchangeRates().effectiveValue(synth, amount, sUSD);
}
_synthsUSD().issue(_feePool().FEE_ADDRESS(), amount);
_feePool().recordFeePaid(amount);
}
}
function _recordLoanAsClosed(Loan storage loan) internal {
loan.amount = 0;
loan.collateral = 0;
loan.accruedInterest = 0;
loan.interestIndex = 0;
loan.lastInteraction = block.timestamp;
}
function _getLoanAndAccrueInterest(uint id, address owner) internal returns (Loan storage loan) {
loan = loans[id];
// Make sure the loan is open and it is the borrower.
_isLoanOpen(loan.interestIndex);
require(loan.account == owner, "Must be borrower");
_accrueInterest(loan);
}
function _checkLoanRatio(Loan storage loan) internal view {
if (loan.amount == 0) {
return;
}
require(collateralRatio(loan.id) > minCratio, "Cratio too low");
}
// ========== MODIFIERS ==========
modifier rateIsValid() {
_requireRateIsValid();
_;
}
function _requireRateIsValid() private view {
require(!_exchangeRates().rateIsInvalid(collateralKey), "Invalid rate");
}
modifier issuanceIsActive() {
_requireIssuanceIsActive();
_;
}
function _requireIssuanceIsActive() private view {
_systemStatus().requireIssuanceActive();
}
// ========== EVENTS ==========
// Setters
event MinCollateralUpdated(uint minCollateral);
event IssueFeeRateUpdated(uint issueFeeRate);
event CanOpenLoansUpdated(bool canOpenLoans);
// Loans
event LoanCreated(address indexed account, uint id, uint amount, uint collateral, bytes32 currency, uint issuanceFee);
event LoanClosed(address indexed account, uint id);
event CollateralDeposited(address indexed account, uint id, uint amountDeposited, uint collateralAfter);
event CollateralWithdrawn(address indexed account, uint id, uint amountWithdrawn, uint collateralAfter);
event LoanRepaymentMade(address indexed account, address indexed repayer, uint id, uint amountRepaid, uint amountAfter);
event LoanDrawnDown(address indexed account, uint id, uint amount);
event LoanPartiallyLiquidated(
address indexed account,
uint id,
address liquidator,
uint amountLiquidated,
uint collateralLiquidated
);
event LoanClosedByLiquidation(
address indexed account,
uint id,
address indexed liquidator,
uint amountLiquidated,
uint collateralLiquidated
);
event LoanClosedByRepayment(address indexed account, uint id, uint amountRepaid, uint collateralAfter);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
pragma experimental ABIEncoderV2;
// Inheritance
import "./Collateral.sol";
import "./ICollateralErc20.sol";
// This contract handles the specific ERC20 implementation details of managing a loan.
contract CollateralErc20 is ICollateralErc20, Collateral {
// The underlying asset for this ERC20 collateral
address public underlyingContract;
uint public underlyingContractDecimals;
constructor(
address _owner,
ICollateralManager _manager,
address _resolver,
bytes32 _collateralKey,
uint _minCratio,
uint _minCollateral,
address _underlyingContract,
uint _underlyingDecimals
) public Collateral(_owner, _manager, _resolver, _collateralKey, _minCratio, _minCollateral) {
underlyingContract = _underlyingContract;
underlyingContractDecimals = _underlyingDecimals;
}
function open(
uint collateral,
uint amount,
bytes32 currency
) external returns (uint id) {
require(collateral <= IERC20(underlyingContract).allowance(msg.sender, address(this)), "Allowance not high enough");
// only transfer the actual collateral
IERC20(underlyingContract).safeTransferFrom(msg.sender, address(this), collateral);
// scale up before entering the system.
uint scaledCollateral = scaleUpCollateral(collateral);
id = _open(scaledCollateral, amount, currency, false);
}
function close(uint id) external returns (uint amount, uint collateral) {
(amount, collateral) = _close(msg.sender, id);
// scale down before transferring back.
uint scaledCollateral = scaleDownCollateral(collateral);
IERC20(underlyingContract).safeTransfer(msg.sender, scaledCollateral);
}
function deposit(
address borrower,
uint id,
uint amount
) external returns (uint principal, uint collateral) {
require(amount <= IERC20(underlyingContract).allowance(msg.sender, address(this)), "Allowance not high enough");
IERC20(underlyingContract).safeTransferFrom(msg.sender, address(this), amount);
// scale up before entering the system.
uint scaledAmount = scaleUpCollateral(amount);
(principal, collateral) = _deposit(borrower, id, scaledAmount);
}
function withdraw(uint id, uint amount) external returns (uint principal, uint collateral) {
// scale up before entering the system.
uint scaledAmount = scaleUpCollateral(amount);
(principal, collateral) = _withdraw(id, scaledAmount);
// scale down before transferring back.
uint scaledWithdraw = scaleDownCollateral(collateral);
IERC20(underlyingContract).safeTransfer(msg.sender, scaledWithdraw);
}
function repay(
address borrower,
uint id,
uint amount
) external returns (uint principal, uint collateral) {
(principal, collateral) = _repay(borrower, msg.sender, id, amount);
}
function draw(uint id, uint amount) external returns (uint principal, uint collateral) {
(principal, collateral) = _draw(id, amount);
}
function liquidate(
address borrower,
uint id,
uint amount
) external {
uint collateralLiquidated = _liquidate(borrower, id, amount);
// scale down before transferring back.
uint scaledCollateral = scaleDownCollateral(collateralLiquidated);
IERC20(underlyingContract).safeTransfer(msg.sender, scaledCollateral);
}
function scaleUpCollateral(uint collateral) public view returns (uint scaledUp) {
uint conversionFactor = 10**uint(SafeMath.sub(18, underlyingContractDecimals));
scaledUp = uint(uint(collateral).mul(conversionFactor));
}
function scaleDownCollateral(uint collateral) public view returns (uint scaledDown) {
uint conversionFactor = 10**uint(SafeMath.sub(18, underlyingContractDecimals));
scaledDown = collateral.div(conversionFactor);
}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
pragma experimental ABIEncoderV2;
// Inheritance
import "./Collateral.sol";
import "./ReentrancyGuard.sol";
import "./ICollateralEth.sol";
// This contract handles the payable aspects of eth loans.
contract CollateralEth is Collateral, ICollateralEth, ReentrancyGuard {
mapping(address => uint) public pendingWithdrawals;
constructor(
address _owner,
ICollateralManager _manager,
address _resolver,
bytes32 _collateralKey,
uint _minCratio,
uint _minCollateral
) public Collateral(_owner, _manager, _resolver, _collateralKey, _minCratio, _minCollateral) {}
function open(uint amount, bytes32 currency) external payable returns (uint id) {
id = _open(msg.value, amount, currency, false);
}
function close(uint id) external returns (uint amount, uint collateral) {
(amount, collateral) = _close(msg.sender, id);
pendingWithdrawals[msg.sender] = pendingWithdrawals[msg.sender].add(collateral);
}
function deposit(address borrower, uint id) external payable returns (uint principal, uint collateral) {
(principal, collateral) = _deposit(borrower, id, msg.value);
}
function withdraw(uint id, uint amount) external returns (uint principal, uint collateral) {
(principal, collateral) = _withdraw(id, amount);
pendingWithdrawals[msg.sender] = pendingWithdrawals[msg.sender].add(amount);
}
function repay(
address borrower,
uint id,
uint amount
) external returns (uint principal, uint collateral) {
(principal, collateral) = _repay(borrower, msg.sender, id, amount);
}
function draw(uint id, uint amount) external returns (uint principal, uint collateral) {
(principal, collateral) = _draw(id, amount);
}
function liquidate(
address borrower,
uint id,
uint amount
) external {
uint collateralLiquidated = _liquidate(borrower, id, amount);
pendingWithdrawals[msg.sender] = pendingWithdrawals[msg.sender].add(collateralLiquidated);
}
function claim(uint amount) external nonReentrant {
// If they try to withdraw more than their total balance, it will fail on the safe sub.
pendingWithdrawals[msg.sender] = pendingWithdrawals[msg.sender].sub(amount);
// solhint-disable avoid-low-level-calls
(bool success, ) = msg.sender.call.value(amount)("");
require(success, "Transfer failed");
}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
// Inheritance
import "./Owned.sol";
import "./Pausable.sol";
import "./MixinResolver.sol";
import "./ICollateralManager.sol";
// Libraries
import "./AddressSetLib.sol";
import "./Bytes32SetLib.sol";
import "./SafeDecimalMath.sol";
// Internal references
import "./CollateralManagerState.sol";
import "./IIssuer.sol";
import "./IExchangeRates.sol";
import "./IERC20.sol";
import "./ISynth.sol";
contract CollateralManager is ICollateralManager, Owned, Pausable, MixinResolver {
/* ========== LIBRARIES ========== */
using SafeMath for uint;
using SafeDecimalMath for uint;
using AddressSetLib for AddressSetLib.AddressSet;
using Bytes32SetLib for Bytes32SetLib.Bytes32Set;
/* ========== CONSTANTS ========== */
bytes32 private constant sUSD = "sUSD";
uint private constant SECONDS_IN_A_YEAR = 31556926 * 1e18;
// Flexible storage names
bytes32 public constant CONTRACT_NAME = "CollateralManager";
bytes32 internal constant COLLATERAL_SYNTHS = "collateralSynth";
/* ========== STATE VARIABLES ========== */
// Stores debt balances and borrow rates.
CollateralManagerState public state;
// The set of all collateral contracts.
AddressSetLib.AddressSet internal _collaterals;
// The set of all available currency keys.
Bytes32SetLib.Bytes32Set internal _currencyKeys;
// The set of all synths issuable by the various collateral contracts
Bytes32SetLib.Bytes32Set internal _synths;
// Map from currency key to synth contract name.
mapping(bytes32 => bytes32) public synthsByKey;
// The set of all synths that are shortable.
Bytes32SetLib.Bytes32Set internal _shortableSynths;
mapping(bytes32 => bytes32) public shortableSynthsByKey;
// The factor that will scale the utilisation ratio.
uint public utilisationMultiplier = 1e18;
// The maximum amount of debt in sUSD that can be issued by non snx collateral.
uint public maxDebt;
// The rate that determines the skew limit maximum.
uint public maxSkewRate;
// The base interest rate applied to all borrows.
uint public baseBorrowRate;
// The base interest rate applied to all shorts.
uint public baseShortRate;
/* ---------- Address Resolver Configuration ---------- */
bytes32 private constant CONTRACT_ISSUER = "Issuer";
bytes32 private constant CONTRACT_EXRATES = "ExchangeRates";
bytes32[24] private addressesToCache = [CONTRACT_ISSUER, CONTRACT_EXRATES];
/* ========== CONSTRUCTOR ========== */
constructor(
CollateralManagerState _state,
address _owner,
address _resolver,
uint _maxDebt,
uint _maxSkewRate,
uint _baseBorrowRate,
uint _baseShortRate
) public Owned(_owner) Pausable() MixinResolver(_resolver) {
owner = msg.sender;
state = _state;
setMaxDebt(_maxDebt);
setMaxSkewRate(_maxSkewRate);
setBaseBorrowRate(_baseBorrowRate);
setBaseShortRate(_baseShortRate);
owner = _owner;
}
/* ========== VIEWS ========== */
function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
bytes32[] memory staticAddresses = new bytes32[](2);
staticAddresses[0] = CONTRACT_ISSUER;
staticAddresses[1] = CONTRACT_EXRATES;
bytes32[] memory shortAddresses;
uint length = _shortableSynths.elements.length;
if (length > 0) {
shortAddresses = new bytes32[](length);
for (uint i = 0; i < length; i++) {
shortAddresses[i] = _shortableSynths.elements[i];
}
}
bytes32[] memory synthAddresses = combineArrays(shortAddresses, _synths.elements);
if (synthAddresses.length > 0) {
addresses = combineArrays(synthAddresses, staticAddresses);
} else {
addresses = staticAddresses;
}
}
// helper function to check whether synth "by key" is a collateral issued by multi-collateral
function isSynthManaged(bytes32 currencyKey) external view returns (bool) {
return synthsByKey[currencyKey] != bytes32(0);
}
/* ---------- Related Contracts ---------- */
function _issuer() internal view returns (IIssuer) {
return IIssuer(requireAndGetAddress(CONTRACT_ISSUER));
}
function _exchangeRates() internal view returns (IExchangeRates) {
return IExchangeRates(requireAndGetAddress(CONTRACT_EXRATES));
}
function _synth(bytes32 synthName) internal view returns (ISynth) {
return ISynth(requireAndGetAddress(synthName));
}
/* ---------- Manager Information ---------- */
function hasCollateral(address collateral) public view returns (bool) {
return _collaterals.contains(collateral);
}
function hasAllCollaterals(address[] memory collaterals) public view returns (bool) {
for (uint i = 0; i < collaterals.length; i++) {
if (!hasCollateral(collaterals[i])) {
return false;
}
}
return true;
}
/* ---------- State Information ---------- */
function long(bytes32 synth) external view returns (uint amount) {
return state.long(synth);
}
function short(bytes32 synth) external view returns (uint amount) {
return state.short(synth);
}
function totalLong() public view returns (uint susdValue, bool anyRateIsInvalid) {
bytes32[] memory synths = _currencyKeys.elements;
if (synths.length > 0) {
for (uint i = 0; i < synths.length; i++) {
bytes32 synth = synths[i];
if (synth == sUSD) {
susdValue = susdValue.add(state.long(synth));
} else {
(uint rate, bool invalid) = _exchangeRates().rateAndInvalid(synth);
uint amount = state.long(synth).multiplyDecimal(rate);
susdValue = susdValue.add(amount);
if (invalid) {
anyRateIsInvalid = true;
}
}
}
}
}
function totalShort() public view returns (uint susdValue, bool anyRateIsInvalid) {
bytes32[] memory synths = _shortableSynths.elements;
if (synths.length > 0) {
for (uint i = 0; i < synths.length; i++) {
bytes32 synth = _synth(synths[i]).currencyKey();
(uint rate, bool invalid) = _exchangeRates().rateAndInvalid(synth);
uint amount = state.short(synth).multiplyDecimal(rate);
susdValue = susdValue.add(amount);
if (invalid) {
anyRateIsInvalid = true;
}
}
}
}
function totalLongAndShort() public view returns (uint susdValue, bool anyRateIsInvalid) {
bytes32[] memory currencyKeys = _currencyKeys.elements;
if (currencyKeys.length > 0) {
(uint[] memory rates, bool invalid) = _exchangeRates().ratesAndInvalidForCurrencies(currencyKeys);
for (uint i = 0; i < rates.length; i++) {
uint longAmount = state.long(currencyKeys[i]).multiplyDecimal(rates[i]);
uint shortAmount = state.short(currencyKeys[i]).multiplyDecimal(rates[i]);
susdValue = susdValue.add(longAmount).add(shortAmount);
if (invalid) {
anyRateIsInvalid = true;
}
}
}
}
function getBorrowRate() public view returns (uint borrowRate, bool anyRateIsInvalid) {
// get the snx backed debt.
uint snxDebt = _issuer().totalIssuedSynths(sUSD, true);
// now get the non snx backed debt.
(uint nonSnxDebt, bool ratesInvalid) = totalLong();
// the total.
uint totalDebt = snxDebt.add(nonSnxDebt);
// now work out the utilisation ratio, and divide through to get a per second value.
uint utilisation = nonSnxDebt.divideDecimal(totalDebt).divideDecimal(SECONDS_IN_A_YEAR);
// scale it by the utilisation multiplier.
uint scaledUtilisation = utilisation.multiplyDecimal(utilisationMultiplier);
// finally, add the base borrow rate.
borrowRate = scaledUtilisation.add(baseBorrowRate);
anyRateIsInvalid = ratesInvalid;
}
function getShortRate(bytes32 synthKey) public view returns (uint shortRate, bool rateIsInvalid) {
rateIsInvalid = _exchangeRates().rateIsInvalid(synthKey);
// Get the long and short supply.
uint longSupply = IERC20(address(_synth(shortableSynthsByKey[synthKey]))).totalSupply();
uint shortSupply = state.short(synthKey);
// In this case, the market is skewed long so its free to short.
if (longSupply > shortSupply) {
return (0, rateIsInvalid);
}
// Otherwise workout the skew towards the short side.
uint skew = shortSupply.sub(longSupply);
// Divide through by the size of the market.
uint proportionalSkew = skew.divideDecimal(longSupply.add(shortSupply)).divideDecimal(SECONDS_IN_A_YEAR);
// Enforce a skew limit maximum.
uint maxSkewLimit = proportionalSkew.multiplyDecimal(maxSkewRate);
// Finally, add the base short rate.
shortRate = maxSkewLimit.add(baseShortRate);
}
function getRatesAndTime(uint index)
public
view
returns (
uint entryRate,
uint lastRate,
uint lastUpdated,
uint newIndex
)
{
(entryRate, lastRate, lastUpdated, newIndex) = state.getRatesAndTime(index);
}
function getShortRatesAndTime(bytes32 currency, uint index)
public
view
returns (
uint entryRate,
uint lastRate,
uint lastUpdated,
uint newIndex
)
{
(entryRate, lastRate, lastUpdated, newIndex) = state.getShortRatesAndTime(currency, index);
}
function exceedsDebtLimit(uint amount, bytes32 currency) external view returns (bool canIssue, bool anyRateIsInvalid) {
uint usdAmount = _exchangeRates().effectiveValue(currency, amount, sUSD);
(uint longAndShortValue, bool invalid) = totalLongAndShort();
return (longAndShortValue.add(usdAmount) <= maxDebt, invalid);
}
/* ========== MUTATIVE FUNCTIONS ========== */
/* ---------- SETTERS ---------- */
function setUtilisationMultiplier(uint _utilisationMultiplier) public onlyOwner {
require(_utilisationMultiplier > 0, "Must be greater than 0");
utilisationMultiplier = _utilisationMultiplier;
emit UtilisationMultiplierUpdated(utilisationMultiplier);
}
function setMaxDebt(uint _maxDebt) public onlyOwner {
require(_maxDebt > 0, "Must be greater than 0");
maxDebt = _maxDebt;
emit MaxDebtUpdated(maxDebt);
}
function setMaxSkewRate(uint _maxSkewRate) public onlyOwner {
maxSkewRate = _maxSkewRate;
emit MaxSkewRateUpdated(maxSkewRate);
}
function setBaseBorrowRate(uint _baseBorrowRate) public onlyOwner {
baseBorrowRate = _baseBorrowRate;
emit BaseBorrowRateUpdated(baseBorrowRate);
}
function setBaseShortRate(uint _baseShortRate) public onlyOwner {
baseShortRate = _baseShortRate;
emit BaseShortRateUpdated(baseShortRate);
}
/* ---------- LOANS ---------- */
function getNewLoanId() external onlyCollateral returns (uint id) {
id = state.incrementTotalLoans();
}
/* ---------- MANAGER ---------- */
function addCollaterals(address[] calldata collaterals) external onlyOwner {
for (uint i = 0; i < collaterals.length; i++) {
if (!_collaterals.contains(collaterals[i])) {
_collaterals.add(collaterals[i]);
emit CollateralAdded(collaterals[i]);
}
}
}
function removeCollaterals(address[] calldata collaterals) external onlyOwner {
for (uint i = 0; i < collaterals.length; i++) {
if (_collaterals.contains(collaterals[i])) {
_collaterals.remove(collaterals[i]);
emit CollateralRemoved(collaterals[i]);
}
}
}
function addSynths(bytes32[] calldata synthNamesInResolver, bytes32[] calldata synthKeys) external onlyOwner {
require(synthNamesInResolver.length == synthKeys.length, "Input array length mismatch");
for (uint i = 0; i < synthNamesInResolver.length; i++) {
if (!_synths.contains(synthNamesInResolver[i])) {
bytes32 synthName = synthNamesInResolver[i];
_synths.add(synthName);
_currencyKeys.add(synthKeys[i]);
synthsByKey[synthKeys[i]] = synthName;
emit SynthAdded(synthName);
}
}
rebuildCache();
}
function areSynthsAndCurrenciesSet(bytes32[] calldata requiredSynthNamesInResolver, bytes32[] calldata synthKeys)
external
view
returns (bool)
{
if (_synths.elements.length != requiredSynthNamesInResolver.length) {
return false;
}
for (uint i = 0; i < requiredSynthNamesInResolver.length; i++) {
if (!_synths.contains(requiredSynthNamesInResolver[i])) {
return false;
}
if (synthsByKey[synthKeys[i]] != requiredSynthNamesInResolver[i]) {
return false;
}
}
return true;
}
function removeSynths(bytes32[] calldata synthNamesInResolver, bytes32[] calldata synthKeys) external onlyOwner {
require(synthNamesInResolver.length == synthKeys.length, "Input array length mismatch");
for (uint i = 0; i < synthNamesInResolver.length; i++) {
if (_synths.contains(synthNamesInResolver[i])) {
// Remove it from the the address set lib.
_synths.remove(synthNamesInResolver[i]);
_currencyKeys.remove(synthKeys[i]);
delete synthsByKey[synthKeys[i]];
emit SynthRemoved(synthNamesInResolver[i]);
}
}
}
function addShortableSynths(bytes32[] calldata requiredSynthNamesInResolver, bytes32[] calldata synthKeys)
external
onlyOwner
{
require(requiredSynthNamesInResolver.length == synthKeys.length, "Input array length mismatch");
for (uint i = 0; i < requiredSynthNamesInResolver.length; i++) {
bytes32 synth = requiredSynthNamesInResolver[i];
if (!_shortableSynths.contains(synth)) {
// Add it to the address set lib.
_shortableSynths.add(synth);
shortableSynthsByKey[synthKeys[i]] = synth;
emit ShortableSynthAdded(synth);
// now the associated synth key to the CollateralManagerState
state.addShortCurrency(synthKeys[i]);
}
}
rebuildCache();
}
function areShortableSynthsSet(bytes32[] calldata requiredSynthNamesInResolver, bytes32[] calldata synthKeys)
external
view
returns (bool)
{
require(requiredSynthNamesInResolver.length == synthKeys.length, "Input array length mismatch");
if (_shortableSynths.elements.length != requiredSynthNamesInResolver.length) {
return false;
}
// now check everything added to external state contract
for (uint i = 0; i < synthKeys.length; i++) {
if (state.getShortRatesLength(synthKeys[i]) == 0) {
return false;
}
}
return true;
}
function removeShortableSynths(bytes32[] calldata synths) external onlyOwner {
for (uint i = 0; i < synths.length; i++) {
if (_shortableSynths.contains(synths[i])) {
// Remove it from the the address set lib.
_shortableSynths.remove(synths[i]);
bytes32 synthKey = _synth(synths[i]).currencyKey();
delete shortableSynthsByKey[synthKey];
state.removeShortCurrency(synthKey);
emit ShortableSynthRemoved(synths[i]);
}
}
}
/* ---------- STATE MUTATIONS ---------- */
function updateBorrowRates(uint rate) internal {
state.updateBorrowRates(rate);
}
function updateShortRates(bytes32 currency, uint rate) internal {
state.updateShortRates(currency, rate);
}
function updateBorrowRatesCollateral(uint rate) external onlyCollateral {
state.updateBorrowRates(rate);
}
function updateShortRatesCollateral(bytes32 currency, uint rate) external onlyCollateral {
state.updateShortRates(currency, rate);
}
function incrementLongs(bytes32 synth, uint amount) external onlyCollateral {
state.incrementLongs(synth, amount);
}
function decrementLongs(bytes32 synth, uint amount) external onlyCollateral {
state.decrementLongs(synth, amount);
}
function incrementShorts(bytes32 synth, uint amount) external onlyCollateral {
state.incrementShorts(synth, amount);
}
function decrementShorts(bytes32 synth, uint amount) external onlyCollateral {
state.decrementShorts(synth, amount);
}
function accrueInterest(
uint interestIndex,
bytes32 currency,
bool isShort
) external onlyCollateral returns (uint difference, uint index) {
// 1. Get the rates we need.
(uint entryRate, uint lastRate, uint lastUpdated, uint newIndex) =
isShort ? getShortRatesAndTime(currency, interestIndex) : getRatesAndTime(interestIndex);
// 2. Get the instantaneous rate.
(uint rate, bool invalid) = isShort ? getShortRate(currency) : getBorrowRate();
require(!invalid, "Invalid rate");
// 3. Get the time since we last updated the rate.
// TODO: consider this in the context of l2 time.
uint timeDelta = block.timestamp.sub(lastUpdated).mul(1e18);
// 4. Get the latest cumulative rate. F_n+1 = F_n + F_last
uint latestCumulative = lastRate.add(rate.multiplyDecimal(timeDelta));
// 5. Return the rate differential and the new interest index.
difference = latestCumulative.sub(entryRate);
index = newIndex;
// 5. Update rates with the lastest cumulative rate. This also updates the time.
isShort ? updateShortRates(currency, latestCumulative) : updateBorrowRates(latestCumulative);
}
/* ========== MODIFIERS ========== */
modifier onlyCollateral {
bool isMultiCollateral = hasCollateral(msg.sender);
require(isMultiCollateral, "Only collateral contracts");
_;
}
// ========== EVENTS ==========
event MaxDebtUpdated(uint maxDebt);
event MaxSkewRateUpdated(uint maxSkewRate);
event LiquidationPenaltyUpdated(uint liquidationPenalty);
event BaseBorrowRateUpdated(uint baseBorrowRate);
event BaseShortRateUpdated(uint baseShortRate);
event UtilisationMultiplierUpdated(uint utilisationMultiplier);
event CollateralAdded(address collateral);
event CollateralRemoved(address collateral);
event SynthAdded(bytes32 synth);
event SynthRemoved(bytes32 synth);
event ShortableSynthAdded(bytes32 synth);
event ShortableSynthRemoved(bytes32 synth);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
pragma experimental ABIEncoderV2;
// Inheritance
import "./Owned.sol";
import "./State.sol";
// Libraries
import "./SafeDecimalMath.sol";
contract CollateralManagerState is Owned, State {
using SafeMath for uint;
using SafeDecimalMath for uint;
struct Balance {
uint long;
uint short;
}
uint public totalLoans;
uint[] public borrowRates;
uint public borrowRatesLastUpdated;
mapping(bytes32 => uint[]) public shortRates;
mapping(bytes32 => uint) public shortRatesLastUpdated;
// The total amount of long and short for a synth,
mapping(bytes32 => Balance) public totalIssuedSynths;
constructor(address _owner, address _associatedContract) public Owned(_owner) State(_associatedContract) {
borrowRates.push(0);
borrowRatesLastUpdated = block.timestamp;
}
function incrementTotalLoans() external onlyAssociatedContract returns (uint) {
totalLoans = totalLoans.add(1);
return totalLoans;
}
function long(bytes32 synth) external view onlyAssociatedContract returns (uint) {
return totalIssuedSynths[synth].long;
}
function short(bytes32 synth) external view onlyAssociatedContract returns (uint) {
return totalIssuedSynths[synth].short;
}
function incrementLongs(bytes32 synth, uint256 amount) external onlyAssociatedContract {
totalIssuedSynths[synth].long = totalIssuedSynths[synth].long.add(amount);
}
function decrementLongs(bytes32 synth, uint256 amount) external onlyAssociatedContract {
totalIssuedSynths[synth].long = totalIssuedSynths[synth].long.sub(amount);
}
function incrementShorts(bytes32 synth, uint256 amount) external onlyAssociatedContract {
totalIssuedSynths[synth].short = totalIssuedSynths[synth].short.add(amount);
}
function decrementShorts(bytes32 synth, uint256 amount) external onlyAssociatedContract {
totalIssuedSynths[synth].short = totalIssuedSynths[synth].short.sub(amount);
}
// Borrow rates, one array here for all currencies.
function getRateAt(uint index) public view returns (uint) {
return borrowRates[index];
}
function getRatesLength() public view returns (uint) {
return borrowRates.length;
}
function updateBorrowRates(uint rate) external onlyAssociatedContract {
borrowRates.push(rate);
borrowRatesLastUpdated = block.timestamp;
}
function ratesLastUpdated() public view returns (uint) {
return borrowRatesLastUpdated;
}
function getRatesAndTime(uint index)
external
view
returns (
uint entryRate,
uint lastRate,
uint lastUpdated,
uint newIndex
)
{
newIndex = getRatesLength();
entryRate = getRateAt(index);
lastRate = getRateAt(newIndex - 1);
lastUpdated = ratesLastUpdated();
}
// Short rates, one array per currency.
function addShortCurrency(bytes32 currency) external onlyAssociatedContract {
if (shortRates[currency].length > 0) {} else {
shortRates[currency].push(0);
shortRatesLastUpdated[currency] = block.timestamp;
}
}
function removeShortCurrency(bytes32 currency) external onlyAssociatedContract {
delete shortRates[currency];
}
function getShortRateAt(bytes32 currency, uint index) internal view returns (uint) {
return shortRates[currency][index];
}
function getShortRatesLength(bytes32 currency) public view returns (uint) {
return shortRates[currency].length;
}
function updateShortRates(bytes32 currency, uint rate) external onlyAssociatedContract {
shortRates[currency].push(rate);
shortRatesLastUpdated[currency] = block.timestamp;
}
function shortRateLastUpdated(bytes32 currency) internal view returns (uint) {
return shortRatesLastUpdated[currency];
}
function getShortRatesAndTime(bytes32 currency, uint index)
external
view
returns (
uint entryRate,
uint lastRate,
uint lastUpdated,
uint newIndex
)
{
newIndex = getShortRatesLength(currency);
entryRate = getShortRateAt(currency, index);
lastRate = getShortRateAt(currency, newIndex - 1);
lastUpdated = shortRateLastUpdated(currency);
}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
pragma experimental ABIEncoderV2;
// Inheritance
import "./Collateral.sol";
contract CollateralShort is Collateral {
constructor(
address _owner,
ICollateralManager _manager,
address _resolver,
bytes32 _collateralKey,
uint _minCratio,
uint _minCollateral
) public Collateral(_owner, _manager, _resolver, _collateralKey, _minCratio, _minCollateral) {}
function open(
uint collateral,
uint amount,
bytes32 currency
) external returns (uint id) {
// Transfer from will throw if they didn't set the allowance
IERC20(address(_synthsUSD())).transferFrom(msg.sender, address(this), collateral);
id = _open(collateral, amount, currency, true);
}
function close(uint id) external returns (uint amount, uint collateral) {
(amount, collateral) = _close(msg.sender, id);
IERC20(address(_synthsUSD())).transfer(msg.sender, collateral);
}
function deposit(
address borrower,
uint id,
uint amount
) external returns (uint principal, uint collateral) {
require(amount <= IERC20(address(_synthsUSD())).allowance(msg.sender, address(this)), "Allowance too low");
IERC20(address(_synthsUSD())).transferFrom(msg.sender, address(this), amount);
(principal, collateral) = _deposit(borrower, id, amount);
}
function withdraw(uint id, uint amount) external returns (uint principal, uint collateral) {
(principal, collateral) = _withdraw(id, amount);
IERC20(address(_synthsUSD())).transfer(msg.sender, amount);
}
function repay(
address borrower,
uint id,
uint amount
) external returns (uint principal, uint collateral) {
(principal, collateral) = _repay(borrower, msg.sender, id, amount);
}
function closeWithCollateral(uint id) external returns (uint amount, uint collateral) {
(amount, collateral) = _closeWithCollateral(msg.sender, id);
if (collateral > 0) {
IERC20(address(_synthsUSD())).transfer(msg.sender, collateral);
}
}
function repayWithCollateral(uint id, uint amount) external returns (uint principal, uint collateral) {
(principal, collateral) = _repayWithCollateral(msg.sender, id, amount);
}
// Needed for Lyra.
function getShortAndCollateral(
address, /* borrower */
uint id
) external view returns (uint principal, uint collateral) {
Loan memory loan = loans[id];
return (loan.amount, loan.collateral);
}
function draw(uint id, uint amount) external returns (uint principal, uint collateral) {
(principal, collateral) = _draw(id, amount);
}
function liquidate(
address borrower,
uint id,
uint amount
) external {
uint collateralLiquidated = _liquidate(borrower, id, amount);
IERC20(address(_synthsUSD())).transfer(msg.sender, collateralLiquidated);
}
function _repayWithCollateral(
address borrower,
uint id,
uint payment
) internal rateIsValid issuanceIsActive returns (uint amount, uint collateral) {
// 0. Get the loan to repay and accrue interest.
Loan storage loan = _getLoanAndAccrueInterest(id, borrower);
// 1. Check loan is open and last interaction time.
_checkLoanAvailable(loan);
// 2. Use the payment to cover accrued interest and reduce debt.
// The returned amounts are the interests paid and the principal component used to reduce debt only.
require(payment <= loan.amount.add(loan.accruedInterest), "Payment too high");
_processPayment(loan, payment);
// 3. Get the equivalent payment amount in sUSD, and also distinguish
// the fee that would be charged for both principal and interest.
(uint expectedAmount, uint exchangeFee, ) = _exchanger().getAmountsForExchange(payment, loan.currency, sUSD);
uint paymentSUSD = expectedAmount.add(exchangeFee);
// 4. Reduce the collateral by the equivalent (total) payment amount in sUSD,
// but add the fee instead of deducting it.
uint collateralToRemove = paymentSUSD.add(exchangeFee);
loan.collateral = loan.collateral.sub(collateralToRemove);
// 5. Pay exchange fees.
_payFees(exchangeFee, sUSD);
// 6. Burn sUSD held in the contract.
_synthsUSD().burn(address(this), collateralToRemove);
// 7. Update the last interaction time.
loan.lastInteraction = block.timestamp;
// 8. Emit the event for the collateral repayment.
emit LoanRepaymentMade(borrower, borrower, id, payment, loan.amount);
// 9. Return the amount repaid and the remaining collateral.
return (payment, loan.collateral);
}
function _closeWithCollateral(address borrower, uint id) internal returns (uint amount, uint collateral) {
// 0. Get the loan to repay and accrue interest.
Loan storage loan = _getLoanAndAccrueInterest(id, borrower);
// 1. Repay the loan with its collateral.
uint amountToRepay = loan.amount.add(loan.accruedInterest);
(amount, collateral) = _repayWithCollateral(borrower, id, amountToRepay);
// 2. Record loan as closed.
_recordLoanAsClosed(loan);
// 3. Emit the event for the loan closed by repayment.
emit LoanClosedByRepayment(borrower, id, amount, collateral);
// 4. Explicitely return the values.
return (amount, collateral);
}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
pragma experimental ABIEncoderV2;
// Inheritance
import "./IAddressResolver.sol";
import "./ICollateralUtil.sol";
import "./ICollateralLoan.sol";
import "./IExchangeRates.sol";
import "./MixinSystemSettings.sol";
import "./SafeDecimalMath.sol";
contract CollateralUtil is ICollateralUtil, ICollateralLoan, MixinSystemSettings {
/* ========== LIBRARIES ========== */
using SafeMath for uint;
using SafeDecimalMath for uint;
/* ========== CONSTANTS ========== */
bytes32 private constant sUSD = "sUSD";
/* ========== ADDRESS RESOLVER CONFIGURATION ========== */
bytes32 private constant CONTRACT_EXRATES = "ExchangeRates";
function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
bytes32[] memory existingAddresses = MixinSystemSettings.resolverAddressesRequired();
bytes32[] memory newAddresses = new bytes32[](1);
newAddresses[0] = CONTRACT_EXRATES;
addresses = combineArrays(existingAddresses, newAddresses);
}
/* ---------- Related Contracts ---------- */
function _exchangeRates() internal view returns (IExchangeRates) {
return IExchangeRates(resolver.requireAndGetAddress(CONTRACT_EXRATES, "Missing ExchangeRates contract"));
}
constructor(address _resolver) public MixinSystemSettings(_resolver) {}
/* ========== UTILITY VIEW FUNCS ========== */
function getCollateralRatio(Loan calldata loan, bytes32 collateralKey) external view returns (uint cratio) {
uint cvalue = _exchangeRates().effectiveValue(collateralKey, loan.collateral, sUSD);
uint dvalue = _exchangeRates().effectiveValue(loan.currency, loan.amount.add(loan.accruedInterest), sUSD);
return cvalue.divideDecimal(dvalue);
}
function maxLoan(
uint amount,
bytes32 currency,
uint minCratio,
bytes32 collateralKey
) external view returns (uint max) {
uint ratio = SafeDecimalMath.unit().divideDecimalRound(minCratio);
return ratio.multiplyDecimal(_exchangeRates().effectiveValue(collateralKey, amount, currency));
}
/**
* r = target issuance ratio
* D = debt value in sUSD
* V = collateral value in sUSD
* P = liquidation penalty
* Calculates amount of synths = (D - V * r) / (1 - (1 + P) * r)
* Note: if you pass a loan in here that is not eligible for liquidation it will revert.
* We check the ratio first in liquidateInternal and only pass eligible loans in.
*/
function liquidationAmount(
Loan calldata loan,
uint minCratio,
bytes32 collateralKey
) external view returns (uint amount) {
uint liquidationPenalty = getLiquidationPenalty();
uint debtValue = _exchangeRates().effectiveValue(loan.currency, loan.amount.add(loan.accruedInterest), sUSD);
uint collateralValue = _exchangeRates().effectiveValue(collateralKey, loan.collateral, sUSD);
uint unit = SafeDecimalMath.unit();
uint dividend = debtValue.sub(collateralValue.divideDecimal(minCratio));
uint divisor = unit.sub(unit.add(liquidationPenalty).divideDecimal(minCratio));
uint sUSDamount = dividend.divideDecimal(divisor);
return _exchangeRates().effectiveValue(sUSD, sUSDamount, loan.currency);
}
function collateralRedeemed(
bytes32 currency,
uint amount,
bytes32 collateralKey
) external view returns (uint collateral) {
uint liquidationPenalty = getLiquidationPenalty();
collateral = _exchangeRates().effectiveValue(currency, amount, collateralKey);
return collateral.multiplyDecimal(SafeDecimalMath.unit().add(liquidationPenalty));
}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
// Internal References
import "./IAddressResolver.sol";
// https://docs.synthetix.io/contracts/source/contracts/contractstorage
contract ContractStorage {
IAddressResolver public resolverProxy;
mapping(bytes32 => bytes32) public hashes;
constructor(address _resolver) internal {
// ReadProxyAddressResolver
resolverProxy = IAddressResolver(_resolver);
}
/* ========== INTERNAL FUNCTIONS ========== */
function _memoizeHash(bytes32 contractName) internal returns (bytes32) {
bytes32 hashKey = hashes[contractName];
if (hashKey == bytes32(0)) {
// set to unique hash at the time of creation
hashKey = keccak256(abi.encodePacked(msg.sender, contractName, block.number));
hashes[contractName] = hashKey;
}
return hashKey;
}
/* ========== VIEWS ========== */
/* ========== RESTRICTED FUNCTIONS ========== */
function migrateContractKey(
bytes32 fromContractName,
bytes32 toContractName,
bool removeAccessFromPreviousContract
) external onlyContract(fromContractName) {
require(hashes[fromContractName] != bytes32(0), "Cannot migrate empty contract");
hashes[toContractName] = hashes[fromContractName];
if (removeAccessFromPreviousContract) {
delete hashes[fromContractName];
}
emit KeyMigrated(fromContractName, toContractName, removeAccessFromPreviousContract);
}
/* ========== MODIFIERS ========== */
modifier onlyContract(bytes32 contractName) {
address callingContract =
resolverProxy.requireAndGetAddress(contractName, "Cannot find contract in Address Resolver");
require(callingContract == msg.sender, "Can only be invoked by the configured contract");
_;
}
/* ========== EVENTS ========== */
event KeyMigrated(bytes32 fromContractName, bytes32 toContractName, bool removeAccessFromPreviousContract);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
import "./Owned.sol";
// https://docs.synthetix.io/contracts/source/contracts/dappmaintenance
/**
* @title DappMaintenance contract.
* @dev When the Synthetix system is on maintenance (upgrade, release...etc) the dApps also need
* to be put on maintenance so no transactions can be done. The DappMaintenance contract is here to keep a state of
* the dApps which indicates if yes or no, they should be up or down.
*/
contract DappMaintenance is Owned {
bool public isPausedStaking = false;
bool public isPausedSX = false;
/**
* @dev Constructor
*/
constructor(address _owner) public Owned(_owner) {
require(_owner != address(0), "Owner address cannot be 0");
owner = _owner;
emit OwnerChanged(address(0), _owner);
}
function setMaintenanceModeAll(bool isPaused) external onlyOwner {
isPausedStaking = isPaused;
isPausedSX = isPaused;
emit StakingMaintenance(isPaused);
emit SXMaintenance(isPaused);
}
function setMaintenanceModeStaking(bool isPaused) external onlyOwner {
isPausedStaking = isPaused;
emit StakingMaintenance(isPausedStaking);
}
function setMaintenanceModeSX(bool isPaused) external onlyOwner {
isPausedSX = isPaused;
emit SXMaintenance(isPausedSX);
}
event StakingMaintenance(bool isPaused);
event SXMaintenance(bool isPaused);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
// Libraries
import "./SafeDecimalMath.sol";
// Inheritance
import "./BaseDebtCache.sol";
// https://docs.synthetix.io/contracts/source/contracts/debtcache
contract DebtCache is BaseDebtCache {
using SafeDecimalMath for uint;
bytes32 public constant CONTRACT_NAME = "DebtCache";
constructor(address _owner, address _resolver) public BaseDebtCache(_owner, _resolver) {}
bytes32 internal constant EXCLUDED_DEBT_KEY = "EXCLUDED_DEBT";
bytes32 internal constant FUTURES_DEBT_KEY = "FUTURES_DEBT";
/* ========== MUTATIVE FUNCTIONS ========== */
// This function exists in case a synth is ever somehow removed without its snapshot being updated.
function purgeCachedSynthDebt(bytes32 currencyKey) external onlyOwner {
require(issuer().synths(currencyKey) == ISynth(0), "Synth exists");
delete _cachedSynthDebt[currencyKey];
}
function takeDebtSnapshot() external requireSystemActiveIfNotOwner {
bytes32[] memory currencyKeys = issuer().availableCurrencyKeys();
(uint[] memory values, uint futuresDebt, uint excludedDebt, bool isInvalid) = _currentSynthDebts(currencyKeys);
// The total SNX-backed debt is the debt of futures markets plus the debt of circulating synths.
uint snxCollateralDebt = futuresDebt;
_cachedSynthDebt[FUTURES_DEBT_KEY] = futuresDebt;
uint numValues = values.length;
for (uint i; i < numValues; i++) {
uint value = values[i];
snxCollateralDebt = snxCollateralDebt.add(value);
_cachedSynthDebt[currencyKeys[i]] = value;
}
// Subtract out the excluded non-SNX backed debt from our total
_cachedSynthDebt[EXCLUDED_DEBT_KEY] = excludedDebt;
uint newDebt = snxCollateralDebt.floorsub(excludedDebt);
_cachedDebt = newDebt;
_cacheTimestamp = block.timestamp;
emit DebtCacheUpdated(newDebt);
emit DebtCacheSnapshotTaken(block.timestamp);
// (in)validate the cache if necessary
_updateDebtCacheValidity(isInvalid);
}
function updateCachedSynthDebts(bytes32[] calldata currencyKeys) external requireSystemActiveIfNotOwner {
(uint[] memory rates, bool anyRateInvalid) = exchangeRates().ratesAndInvalidForCurrencies(currencyKeys);
_updateCachedSynthDebtsWithRates(currencyKeys, rates, anyRateInvalid);
}
function updateCachedSynthDebtWithRate(bytes32 currencyKey, uint currencyRate) external onlyIssuer {
bytes32[] memory synthKeyArray = new bytes32[](1);
synthKeyArray[0] = currencyKey;
uint[] memory synthRateArray = new uint[](1);
synthRateArray[0] = currencyRate;
_updateCachedSynthDebtsWithRates(synthKeyArray, synthRateArray, false);
}
function updateCachedSynthDebtsWithRates(bytes32[] calldata currencyKeys, uint[] calldata currencyRates)
external
onlyIssuerOrExchanger
{
_updateCachedSynthDebtsWithRates(currencyKeys, currencyRates, false);
}
function updateDebtCacheValidity(bool currentlyInvalid) external onlyIssuer {
_updateDebtCacheValidity(currentlyInvalid);
}
function recordExcludedDebtChange(bytes32 currencyKey, int256 delta) external onlyDebtIssuer {
int256 newExcludedDebt = int256(_excludedIssuedDebt[currencyKey]) + delta;
require(newExcludedDebt >= 0, "Excluded debt cannot become negative");
_excludedIssuedDebt[currencyKey] = uint(newExcludedDebt);
}
function updateCachedsUSDDebt(int amount) external onlyIssuer {
uint delta = SafeDecimalMath.abs(amount);
if (amount > 0) {
_cachedSynthDebt[sUSD] = _cachedSynthDebt[sUSD].add(delta);
_cachedDebt = _cachedDebt.add(delta);
} else {
_cachedSynthDebt[sUSD] = _cachedSynthDebt[sUSD].sub(delta);
_cachedDebt = _cachedDebt.sub(delta);
}
emit DebtCacheUpdated(_cachedDebt);
}
/* ========== INTERNAL FUNCTIONS ========== */
function _updateDebtCacheValidity(bool currentlyInvalid) internal {
if (_cacheInvalid != currentlyInvalid) {
_cacheInvalid = currentlyInvalid;
emit DebtCacheValidityChanged(currentlyInvalid);
}
}
// Updated the global debt according to a rate/supply change in a subset of issued synths.
function _updateCachedSynthDebtsWithRates(
bytes32[] memory currencyKeys,
uint[] memory currentRates,
bool anyRateIsInvalid
) internal {
uint numKeys = currencyKeys.length;
require(numKeys == currentRates.length, "Input array lengths differ");
// Compute the cached and current debt sum for the subset of synths provided.
uint cachedSum;
uint currentSum;
uint[] memory currentValues = _issuedSynthValues(currencyKeys, currentRates);
for (uint i = 0; i < numKeys; i++) {
bytes32 key = currencyKeys[i];
uint currentSynthDebt = currentValues[i];
cachedSum = cachedSum.add(_cachedSynthDebt[key]);
currentSum = currentSum.add(currentSynthDebt);
_cachedSynthDebt[key] = currentSynthDebt;
}
// Apply the debt update.
if (cachedSum != currentSum) {
uint debt = _cachedDebt;
// apply the delta between the cachedSum and currentSum
// add currentSum before sub cachedSum to prevent overflow as cachedSum > debt for large amount of excluded debt
debt = debt.add(currentSum).sub(cachedSum);
_cachedDebt = debt;
emit DebtCacheUpdated(debt);
}
// Invalidate the cache if necessary
if (anyRateIsInvalid) {
_updateDebtCacheValidity(anyRateIsInvalid);
}
}
/* ========== EVENTS ========== */
event DebtCacheUpdated(uint cachedDebt);
event DebtCacheSnapshotTaken(uint timestamp);
event DebtCacheValidityChanged(bool indexed isInvalid);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
pragma experimental ABIEncoderV2;
// Inheritance
import "./BaseDebtMigrator.sol";
// Internal references
import "./IDebtMigrator.sol";
import "./ILiquidator.sol";
import "./ILiquidatorRewards.sol";
import "./ISynthetixBridgeToOptimism.sol";
import "./ISynthetixDebtShare.sol";
contract DebtMigratorOnEthereum is BaseDebtMigrator {
/* ========== ADDRESS RESOLVER CONFIGURATION ========== */
bytes32 private constant CONTRACT_OVM_DEBT_MIGRATOR_ON_OPTIMISM = "ovm:DebtMigratorOnOptimism";
bytes32 private constant CONTRACT_LIQUIDATOR = "Liquidator";
bytes32 private constant CONTRACT_LIQUIDATOR_REWARDS = "LiquidatorRewards";
bytes32 private constant CONTRACT_SYNTHETIX_BRIDGE_TO_OPTIMISM = "SynthetixBridgeToOptimism";
bytes32 private constant CONTRACT_SYNTHETIX_DEBT_SHARE = "SynthetixDebtShare";
function CONTRACT_NAME() public pure returns (bytes32) {
return "DebtMigratorOnEthereum";
}
bool public initiationActive;
/* ========== CONSTRUCTOR ========== */
constructor(address _owner, address _resolver) public BaseDebtMigrator(_owner, _resolver) {}
/* ========== VIEWS ============ */
function _debtMigratorOnOptimism() private view returns (address) {
return requireAndGetAddress(CONTRACT_OVM_DEBT_MIGRATOR_ON_OPTIMISM);
}
function _liquidator() internal view returns (ILiquidator) {
return ILiquidator(requireAndGetAddress(CONTRACT_LIQUIDATOR));
}
function _liquidatorRewards() internal view returns (ILiquidatorRewards) {
return ILiquidatorRewards(requireAndGetAddress(CONTRACT_LIQUIDATOR_REWARDS));
}
function _synthetixBridgeToOptimism() internal view returns (ISynthetixBridgeToOptimism) {
return ISynthetixBridgeToOptimism(requireAndGetAddress(CONTRACT_SYNTHETIX_BRIDGE_TO_OPTIMISM));
}
function _synthetixDebtShare() internal view returns (ISynthetixDebtShare) {
return ISynthetixDebtShare(requireAndGetAddress(CONTRACT_SYNTHETIX_DEBT_SHARE));
}
function _initiatingActive() internal view {
require(initiationActive, "Initiation deactivated");
}
function _getCrossDomainGasLimit(uint32 crossDomainGasLimit) private view returns (uint32) {
// Use specified crossDomainGasLimit if specified value is not zero.
// otherwise use the default in SystemSettings.
return
crossDomainGasLimit != 0
? crossDomainGasLimit
: uint32(getCrossDomainMessageGasLimit(CrossDomainMessageGasLimits.Relay));
}
function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
bytes32[] memory existingAddresses = BaseDebtMigrator.resolverAddressesRequired();
bytes32[] memory newAddresses = new bytes32[](5);
newAddresses[0] = CONTRACT_OVM_DEBT_MIGRATOR_ON_OPTIMISM;
newAddresses[1] = CONTRACT_LIQUIDATOR;
newAddresses[2] = CONTRACT_LIQUIDATOR_REWARDS;
newAddresses[3] = CONTRACT_SYNTHETIX_BRIDGE_TO_OPTIMISM;
newAddresses[4] = CONTRACT_SYNTHETIX_DEBT_SHARE;
addresses = combineArrays(existingAddresses, newAddresses);
}
/* ========== MUTATIVE ========== */
// Ideally, the account should call vest on their escrow before invoking the debt migration to L2.
function migrateDebt(address account) public requireInitiationActive {
require(msg.sender == account, "Must be the account owner");
_migrateDebt(account);
}
function _migrateDebt(address _account) internal {
// Require the account to not be flagged or open for liquidation
require(!_liquidator().isLiquidationOpen(_account, false), "Cannot migrate if open for liquidation");
// Important: this has to happen before any updates to user's debt shares
_liquidatorRewards().getReward(_account);
// First, remove all debt shares on L1
ISynthetixDebtShare sds = _synthetixDebtShare();
uint totalDebtShares = sds.balanceOf(_account);
require(totalDebtShares > 0, "No debt to migrate");
// Increment the in-flight debt counter by their SDS balance
_incrementDebtTransferCounter(DEBT_TRANSFER_SENT, totalDebtShares);
_issuer().modifyDebtSharesForMigration(_account, totalDebtShares);
// Deposit all of the liquid & revoked escrowed SNX to the migrator on L2
(uint totalEscrowRevoked, uint totalLiquidBalance) =
ISynthetix(requireAndGetAddress(CONTRACT_SYNTHETIX)).migrateAccountBalances(_account);
uint totalAmountToDeposit = totalLiquidBalance.add(totalEscrowRevoked);
require(totalAmountToDeposit > 0, "Cannot migrate zero balances");
require(
resolver.getAddress(CONTRACT_OVM_DEBT_MIGRATOR_ON_OPTIMISM) != address(0),
"Debt Migrator On Optimism not set"
);
_synthetixERC20().approve(address(_synthetixBridgeToOptimism()), totalAmountToDeposit);
_synthetixBridgeToOptimism().depositTo(_debtMigratorOnOptimism(), totalAmountToDeposit);
// Require all zeroed balances
require(_synthetixDebtShare().balanceOf(_account) == 0, "Debt share balance is not zero");
require(_synthetixERC20().balanceOf(_account) == 0, "SNX balance is not zero");
require(_rewardEscrowV2().balanceOf(_account) == 0, "Escrow balanace is not zero");
require(_liquidatorRewards().earned(_account) == 0, "Earned balance is not zero");
// Create the data payloads to be relayed on L2
IIssuer issuer;
bytes memory _debtPayload =
abi.encodeWithSelector(issuer.modifyDebtSharesForMigration.selector, _account, totalDebtShares);
// Send a message with the debt & escrow payloads to L2 to finalize the migration
IDebtMigrator debtMigratorOnOptimism;
bytes memory messageData =
abi.encodeWithSelector(
debtMigratorOnOptimism.finalizeDebtMigration.selector,
_account,
totalDebtShares,
totalEscrowRevoked,
totalLiquidBalance,
_debtPayload
);
_messenger().sendMessage(_debtMigratorOnOptimism(), messageData, _getCrossDomainGasLimit(0)); // passing zero will use the system setting default
emit MigrationInitiated(_account, totalDebtShares, totalEscrowRevoked, totalLiquidBalance);
}
/* ========= RESTRICTED ========= */
function suspendInitiation() external onlyOwner {
require(initiationActive, "Initiation suspended");
initiationActive = false;
emit InitiationSuspended();
}
function resumeInitiation() external onlyOwner {
require(!initiationActive, "Initiation not suspended");
initiationActive = true;
emit InitiationResumed();
}
/* ========= MODIFIERS ========= */
modifier requireInitiationActive() {
_initiatingActive();
_;
}
/* ========== EVENTS ========== */
event InitiationSuspended();
event InitiationResumed();
event MigrationInitiated(
address indexed account,
uint totalDebtSharesMigrated,
uint totalEscrowMigrated,
uint totalLiquidBalanceMigrated
);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
pragma experimental ABIEncoderV2;
// Inheritance
import "./BaseDebtMigrator.sol";
import "./IDebtMigrator.sol";
contract DebtMigratorOnOptimism is BaseDebtMigrator, IDebtMigrator {
/* ========== ADDRESS RESOLVER CONFIGURATION ========== */
bytes32 private constant CONTRACT_BASE_DEBT_MIGRATOR_ON_ETHEREUM = "base:DebtMigratorOnEthereum";
function CONTRACT_NAME() public pure returns (bytes32) {
return "DebtMigratorOnOptimism";
}
/* ========== CONSTRUCTOR ============ */
constructor(address _owner, address _resolver) public BaseDebtMigrator(_owner, _resolver) {}
/* ========== VIEWS ========== */
function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
bytes32[] memory existingAddresses = BaseDebtMigrator.resolverAddressesRequired();
bytes32[] memory newAddresses = new bytes32[](1);
newAddresses[0] = CONTRACT_BASE_DEBT_MIGRATOR_ON_ETHEREUM;
addresses = combineArrays(existingAddresses, newAddresses);
}
function _debtMigratorOnEthereum() private view returns (address) {
return requireAndGetAddress(CONTRACT_BASE_DEBT_MIGRATOR_ON_ETHEREUM);
}
function _counterpart() internal view returns (address) {
return _debtMigratorOnEthereum();
}
/* ========== MUTATIVE ============ */
function _finalizeDebt(bytes memory _debtPayload) private {
address target = address(_issuer()); // target is the Issuer contract on Optimism.
// solhint-disable avoid-low-level-calls
(bool success, bytes memory result) = target.call(_debtPayload);
require(success, string(abi.encode("finalize debt call failed:", result)));
}
function _finalizeEscrow(address account, uint escrowMigrated) private {
uint numEntries = 10;
uint duration = 8 weeks;
// Split up the full amount of migrated escrow into ten chunks.
uint amountPerEntry = escrowMigrated.multiplyDecimal(1e17);
// Make sure to approve the creation of the escrow entries.
_synthetixERC20().approve(address(_rewardEscrowV2()), escrowMigrated);
// Create ten distinct entries that vest each month for a year. First entry vests in 8 weeks.
uint amountEscrowed = 0;
for (uint i = 0; i < numEntries; i++) {
if (i == numEntries - 1) {
// Use the remaining amount of escrow for the last entry to avoid rounding issues.
uint remaining = escrowMigrated.sub(amountEscrowed);
_rewardEscrowV2().createEscrowEntry(account, remaining, duration);
} else {
_rewardEscrowV2().createEscrowEntry(account, amountPerEntry, duration);
}
duration += 4 weeks;
amountEscrowed += amountPerEntry;
}
}
/* ========== MODIFIERS ============ */
function _onlyAllowFromCounterpart() internal view {
iAbs_BaseCrossDomainMessenger messenger = _messenger();
require(msg.sender == address(messenger), "Sender is not the messenger");
require(messenger.xDomainMessageSender() == _counterpart(), "L1 sender is not the debt migrator");
}
modifier onlyCounterpart() {
_onlyAllowFromCounterpart();
_;
}
/* ========== EXTERNAL ========== */
function finalizeDebtMigration(
address account,
uint debtSharesMigrated,
uint escrowMigrated,
uint liquidSnxMigrated,
bytes calldata debtPayload
) external onlyCounterpart {
_incrementDebtTransferCounter(DEBT_TRANSFER_RECV, debtSharesMigrated);
_finalizeDebt(debtPayload);
if (escrowMigrated > 0) {
_finalizeEscrow(account, escrowMigrated);
}
if (liquidSnxMigrated > 0) {
_synthetixERC20().transfer(account, liquidSnxMigrated);
}
emit MigrationFinalized(account, debtSharesMigrated, escrowMigrated, liquidSnxMigrated);
}
/* ========== EVENTS ========== */
event MigrationFinalized(
address indexed account,
uint totalDebtSharesMigrated,
uint totalEscrowMigrated,
uint totalLiquidBalanceMigrated
);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
// Inheritance
import "./Owned.sol";
import "./IDelegateApprovals.sol";
// Internal references
import "./EternalStorage.sol";
// https://docs.synthetix.io/contracts/source/contracts/delegateapprovals
contract DelegateApprovals is Owned, IDelegateApprovals {
bytes32 public constant BURN_FOR_ADDRESS = "BurnForAddress";
bytes32 public constant ISSUE_FOR_ADDRESS = "IssueForAddress";
bytes32 public constant CLAIM_FOR_ADDRESS = "ClaimForAddress";
bytes32 public constant EXCHANGE_FOR_ADDRESS = "ExchangeForAddress";
bytes32 public constant APPROVE_ALL = "ApproveAll";
bytes32[5] private _delegatableFunctions = [
APPROVE_ALL,
BURN_FOR_ADDRESS,
ISSUE_FOR_ADDRESS,
CLAIM_FOR_ADDRESS,
EXCHANGE_FOR_ADDRESS
];
/* ========== STATE VARIABLES ========== */
EternalStorage public eternalStorage;
constructor(address _owner, EternalStorage _eternalStorage) public Owned(_owner) {
eternalStorage = _eternalStorage;
}
/* ========== VIEWS ========== */
// Move it to setter and associatedState
// util to get key based on action name + address of authoriser + address for delegate
function _getKey(
bytes32 _action,
address _authoriser,
address _delegate
) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(_action, _authoriser, _delegate));
}
// hash of actionName + address of authoriser + address for the delegate
function canBurnFor(address authoriser, address delegate) external view returns (bool) {
return _checkApproval(BURN_FOR_ADDRESS, authoriser, delegate);
}
function canIssueFor(address authoriser, address delegate) external view returns (bool) {
return _checkApproval(ISSUE_FOR_ADDRESS, authoriser, delegate);
}
function canClaimFor(address authoriser, address delegate) external view returns (bool) {
return _checkApproval(CLAIM_FOR_ADDRESS, authoriser, delegate);
}
function canExchangeFor(address authoriser, address delegate) external view returns (bool) {
return _checkApproval(EXCHANGE_FOR_ADDRESS, authoriser, delegate);
}
function approvedAll(address authoriser, address delegate) public view returns (bool) {
return eternalStorage.getBooleanValue(_getKey(APPROVE_ALL, authoriser, delegate));
}
// internal function to check approval based on action
// if approved for all actions then will return true
// before checking specific approvals
function _checkApproval(
bytes32 action,
address authoriser,
address delegate
) internal view returns (bool) {
if (approvedAll(authoriser, delegate)) return true;
return eternalStorage.getBooleanValue(_getKey(action, authoriser, delegate));
}
/* ========== SETTERS ========== */
// Approve All
function approveAllDelegatePowers(address delegate) external {
_setApproval(APPROVE_ALL, msg.sender, delegate);
}
// Removes all delegate approvals
function removeAllDelegatePowers(address delegate) external {
for (uint i = 0; i < _delegatableFunctions.length; i++) {
_withdrawApproval(_delegatableFunctions[i], msg.sender, delegate);
}
}
// Burn on behalf
function approveBurnOnBehalf(address delegate) external {
_setApproval(BURN_FOR_ADDRESS, msg.sender, delegate);
}
function removeBurnOnBehalf(address delegate) external {
_withdrawApproval(BURN_FOR_ADDRESS, msg.sender, delegate);
}
// Issue on behalf
function approveIssueOnBehalf(address delegate) external {
_setApproval(ISSUE_FOR_ADDRESS, msg.sender, delegate);
}
function removeIssueOnBehalf(address delegate) external {
_withdrawApproval(ISSUE_FOR_ADDRESS, msg.sender, delegate);
}
// Claim on behalf
function approveClaimOnBehalf(address delegate) external {
_setApproval(CLAIM_FOR_ADDRESS, msg.sender, delegate);
}
function removeClaimOnBehalf(address delegate) external {
_withdrawApproval(CLAIM_FOR_ADDRESS, msg.sender, delegate);
}
// Exchange on behalf
function approveExchangeOnBehalf(address delegate) external {
_setApproval(EXCHANGE_FOR_ADDRESS, msg.sender, delegate);
}
function removeExchangeOnBehalf(address delegate) external {
_withdrawApproval(EXCHANGE_FOR_ADDRESS, msg.sender, delegate);
}
function _setApproval(
bytes32 action,
address authoriser,
address delegate
) internal {
require(delegate != address(0), "Can't delegate to address(0)");
eternalStorage.setBooleanValue(_getKey(action, authoriser, delegate), true);
emit Approval(authoriser, delegate, action);
}
function _withdrawApproval(
bytes32 action,
address authoriser,
address delegate
) internal {
// Check approval is set otherwise skip deleting approval
if (eternalStorage.getBooleanValue(_getKey(action, authoriser, delegate))) {
eternalStorage.deleteBooleanValue(_getKey(action, authoriser, delegate));
emit WithdrawApproval(authoriser, delegate, action);
}
}
function setEternalStorage(EternalStorage _eternalStorage) external onlyOwner {
require(address(_eternalStorage) != address(0), "Can't set eternalStorage to address(0)");
eternalStorage = _eternalStorage;
emit EternalStorageUpdated(address(eternalStorage));
}
/* ========== EVENTS ========== */
event Approval(address indexed authoriser, address delegate, bytes32 action);
event WithdrawApproval(address indexed authoriser, address delegate, bytes32 action);
event EternalStorageUpdated(address newEternalStorage);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
// Inheritance
import "./Owned.sol";
import "./Pausable.sol";
import "./ReentrancyGuard.sol";
import "./MixinResolver.sol";
import "./IDepot.sol";
// Libraries
import "./SafeDecimalMath.sol";
// Internal references
import "./IERC20.sol";
import "./IExchangeRates.sol";
// https://docs.synthetix.io/contracts/source/contracts/depot
contract Depot is Owned, Pausable, ReentrancyGuard, MixinResolver, IDepot {
using SafeMath for uint;
using SafeDecimalMath for uint;
bytes32 internal constant SNX = "SNX";
bytes32 internal constant ETH = "ETH";
/* ========== STATE VARIABLES ========== */
// Address where the ether and Synths raised for selling SNX is transfered to
// Any ether raised for selling Synths gets sent back to whoever deposited the Synths,
// and doesn't have anything to do with this address.
address payable public fundsWallet;
/* Stores deposits from users. */
struct SynthDepositEntry {
// The user that made the deposit
address payable user;
// The amount (in Synths) that they deposited
uint amount;
}
/* User deposits are sold on a FIFO (First in First out) basis. When users deposit
synths with us, they get added this queue, which then gets fulfilled in order.
Conceptually this fits well in an array, but then when users fill an order we
end up copying the whole array around, so better to use an index mapping instead
for gas performance reasons.
The indexes are specified (inclusive, exclusive), so (0, 0) means there's nothing
in the array, and (3, 6) means there are 3 elements at 3, 4, and 5. You can obtain
the length of the "array" by querying depositEndIndex - depositStartIndex. All index
operations use safeAdd, so there is no way to overflow, so that means there is a
very large but finite amount of deposits this contract can handle before it fills up. */
mapping(uint => SynthDepositEntry) public deposits;
// The starting index of our queue inclusive
uint public depositStartIndex;
// The ending index of our queue exclusive
uint public depositEndIndex;
/* This is a convenience variable so users and dApps can just query how much sUSD
we have available for purchase without having to iterate the mapping with a
O(n) amount of calls for something we'll probably want to display quite regularly. */
uint public totalSellableDeposits;
// The minimum amount of sUSD required to enter the FiFo queue
uint public minimumDepositAmount = 50 * SafeDecimalMath.unit();
// A cap on the amount of sUSD you can buy with ETH in 1 transaction
uint public maxEthPurchase = 500 * SafeDecimalMath.unit();
// If a user deposits a synth amount < the minimumDepositAmount the contract will keep
// the total of small deposits which will not be sold on market and the sender
// must call withdrawMyDepositedSynths() to get them back.
mapping(address => uint) public smallDeposits;
/* ========== ADDRESS RESOLVER CONFIGURATION ========== */
bytes32 private constant CONTRACT_SYNTHSUSD = "SynthsUSD";
bytes32 private constant CONTRACT_EXRATES = "ExchangeRates";
bytes32 private constant CONTRACT_SYNTHETIX = "Synthetix";
/* ========== CONSTRUCTOR ========== */
constructor(
address _owner,
address payable _fundsWallet,
address _resolver
) public Owned(_owner) Pausable() MixinResolver(_resolver) {
fundsWallet = _fundsWallet;
}
/* ========== SETTERS ========== */
function setMaxEthPurchase(uint _maxEthPurchase) external onlyOwner {
maxEthPurchase = _maxEthPurchase;
emit MaxEthPurchaseUpdated(maxEthPurchase);
}
/**
* @notice Set the funds wallet where ETH raised is held
* @param _fundsWallet The new address to forward ETH and Synths to
*/
function setFundsWallet(address payable _fundsWallet) external onlyOwner {
fundsWallet = _fundsWallet;
emit FundsWalletUpdated(fundsWallet);
}
/**
* @notice Set the minimum deposit amount required to depoist sUSD into the FIFO queue
* @param _amount The new new minimum number of sUSD required to deposit
*/
function setMinimumDepositAmount(uint _amount) external onlyOwner {
// Do not allow us to set it less than 1 dollar opening up to fractional desposits in the queue again
require(_amount > SafeDecimalMath.unit(), "Minimum deposit amount must be greater than UNIT");
minimumDepositAmount = _amount;
emit MinimumDepositAmountUpdated(minimumDepositAmount);
}
/* ========== MUTATIVE FUNCTIONS ========== */
/**
* @notice Fallback function (exchanges ETH to sUSD)
*/
function() external payable nonReentrant rateNotInvalid(ETH) notPaused {
_exchangeEtherForSynths();
}
/**
* @notice Exchange ETH to sUSD.
*/
/* solhint-disable multiple-sends, reentrancy */
function exchangeEtherForSynths()
external
payable
nonReentrant
rateNotInvalid(ETH)
notPaused
returns (
uint // Returns the number of Synths (sUSD) received
)
{
return _exchangeEtherForSynths();
}
function _exchangeEtherForSynths() internal returns (uint) {
require(msg.value <= maxEthPurchase, "ETH amount above maxEthPurchase limit");
uint ethToSend;
// The multiplication works here because exchangeRates().rateForCurrency(ETH) is specified in
// 18 decimal places, just like our currency base.
uint requestedToPurchase = msg.value.multiplyDecimal(exchangeRates().rateForCurrency(ETH));
uint remainingToFulfill = requestedToPurchase;
// Iterate through our outstanding deposits and sell them one at a time.
for (uint i = depositStartIndex; remainingToFulfill > 0 && i < depositEndIndex; i++) {
SynthDepositEntry memory deposit = deposits[i];
// If it's an empty spot in the queue from a previous withdrawal, just skip over it and
// update the queue. It's already been deleted.
if (deposit.user == address(0)) {
depositStartIndex = depositStartIndex.add(1);
} else {
// If the deposit can more than fill the order, we can do this
// without touching the structure of our queue.
if (deposit.amount > remainingToFulfill) {
// Ok, this deposit can fulfill the whole remainder. We don't need
// to change anything about our queue we can just fulfill it.
// Subtract the amount from our deposit and total.
uint newAmount = deposit.amount.sub(remainingToFulfill);
deposits[i] = SynthDepositEntry({user: deposit.user, amount: newAmount});
totalSellableDeposits = totalSellableDeposits.sub(remainingToFulfill);
// Transfer the ETH to the depositor. Send is used instead of transfer
// so a non payable contract won't block the FIFO queue on a failed
// ETH payable for synths transaction. The proceeds to be sent to the
// synthetix foundation funds wallet. This is to protect all depositors
// in the queue in this rare case that may occur.
ethToSend = remainingToFulfill.divideDecimal(exchangeRates().rateForCurrency(ETH));
// We need to use send here instead of transfer because transfer reverts
// if the recipient is a non-payable contract. Send will just tell us it
// failed by returning false at which point we can continue.
if (!deposit.user.send(ethToSend)) {
fundsWallet.transfer(ethToSend);
emit NonPayableContract(deposit.user, ethToSend);
} else {
emit ClearedDeposit(msg.sender, deposit.user, ethToSend, remainingToFulfill, i);
}
// And the Synths to the recipient.
// Note: Fees are calculated by the Synth contract, so when
// we request a specific transfer here, the fee is
// automatically deducted and sent to the fee pool.
synthsUSD().transfer(msg.sender, remainingToFulfill);
// And we have nothing left to fulfill on this order.
remainingToFulfill = 0;
} else if (deposit.amount <= remainingToFulfill) {
// We need to fulfill this one in its entirety and kick it out of the queue.
// Start by kicking it out of the queue.
// Free the storage because we can.
delete deposits[i];
// Bump our start index forward one.
depositStartIndex = depositStartIndex.add(1);
// We also need to tell our total it's decreased
totalSellableDeposits = totalSellableDeposits.sub(deposit.amount);
// Now fulfill by transfering the ETH to the depositor. Send is used instead of transfer
// so a non payable contract won't block the FIFO queue on a failed
// ETH payable for synths transaction. The proceeds to be sent to the
// synthetix foundation funds wallet. This is to protect all depositors
// in the queue in this rare case that may occur.
ethToSend = deposit.amount.divideDecimal(exchangeRates().rateForCurrency(ETH));
// We need to use send here instead of transfer because transfer reverts
// if the recipient is a non-payable contract. Send will just tell us it
// failed by returning false at which point we can continue.
if (!deposit.user.send(ethToSend)) {
fundsWallet.transfer(ethToSend);
emit NonPayableContract(deposit.user, ethToSend);
} else {
emit ClearedDeposit(msg.sender, deposit.user, ethToSend, deposit.amount, i);
}
// And the Synths to the recipient.
// Note: Fees are calculated by the Synth contract, so when
// we request a specific transfer here, the fee is
// automatically deducted and sent to the fee pool.
synthsUSD().transfer(msg.sender, deposit.amount);
// And subtract the order from our outstanding amount remaining
// for the next iteration of the loop.
remainingToFulfill = remainingToFulfill.sub(deposit.amount);
}
}
}
// Ok, if we're here and 'remainingToFulfill' isn't zero, then
// we need to refund the remainder of their ETH back to them.
if (remainingToFulfill > 0) {
msg.sender.transfer(remainingToFulfill.divideDecimal(exchangeRates().rateForCurrency(ETH)));
}
// How many did we actually give them?
uint fulfilled = requestedToPurchase.sub(remainingToFulfill);
if (fulfilled > 0) {
// Now tell everyone that we gave them that many (only if the amount is greater than 0).
emit Exchange("ETH", msg.value, "sUSD", fulfilled);
}
return fulfilled;
}
/* solhint-enable multiple-sends, reentrancy */
/**
* @notice Exchange ETH to sUSD while insisting on a particular rate. This allows a user to
* exchange while protecting against frontrunning by the contract owner on the exchange rate.
* @param guaranteedRate The exchange rate (ether price) which must be honored or the call will revert.
*/
function exchangeEtherForSynthsAtRate(uint guaranteedRate)
external
payable
rateNotInvalid(ETH)
notPaused
returns (
uint // Returns the number of Synths (sUSD) received
)
{
require(guaranteedRate == exchangeRates().rateForCurrency(ETH), "Guaranteed rate would not be received");
return _exchangeEtherForSynths();
}
function _exchangeEtherForSNX() internal returns (uint) {
// How many SNX are they going to be receiving?
uint synthetixToSend = synthetixReceivedForEther(msg.value);
// Store the ETH in our funds wallet
fundsWallet.transfer(msg.value);
// And send them the SNX.
synthetix().transfer(msg.sender, synthetixToSend);
emit Exchange("ETH", msg.value, "SNX", synthetixToSend);
return synthetixToSend;
}
/**
* @notice Exchange ETH to SNX.
*/
function exchangeEtherForSNX()
external
payable
rateNotInvalid(SNX)
rateNotInvalid(ETH)
notPaused
returns (
uint // Returns the number of SNX received
)
{
return _exchangeEtherForSNX();
}
/**
* @notice Exchange ETH to SNX while insisting on a particular set of rates. This allows a user to
* exchange while protecting against frontrunning by the contract owner on the exchange rates.
* @param guaranteedEtherRate The ether exchange rate which must be honored or the call will revert.
* @param guaranteedSynthetixRate The synthetix exchange rate which must be honored or the call will revert.
*/
function exchangeEtherForSNXAtRate(uint guaranteedEtherRate, uint guaranteedSynthetixRate)
external
payable
rateNotInvalid(SNX)
rateNotInvalid(ETH)
notPaused
returns (
uint // Returns the number of SNX received
)
{
require(guaranteedEtherRate == exchangeRates().rateForCurrency(ETH), "Guaranteed ether rate would not be received");
require(
guaranteedSynthetixRate == exchangeRates().rateForCurrency(SNX),
"Guaranteed synthetix rate would not be received"
);
return _exchangeEtherForSNX();
}
function _exchangeSynthsForSNX(uint synthAmount) internal returns (uint) {
// How many SNX are they going to be receiving?
uint synthetixToSend = synthetixReceivedForSynths(synthAmount);
// Ok, transfer the Synths to our funds wallet.
// These do not go in the deposit queue as they aren't for sale as such unless
// they're sent back in from the funds wallet.
synthsUSD().transferFrom(msg.sender, fundsWallet, synthAmount);
// And send them the SNX.
synthetix().transfer(msg.sender, synthetixToSend);
emit Exchange("sUSD", synthAmount, "SNX", synthetixToSend);
return synthetixToSend;
}
/**
* @notice Exchange sUSD for SNX
* @param synthAmount The amount of synths the user wishes to exchange.
*/
function exchangeSynthsForSNX(uint synthAmount)
external
rateNotInvalid(SNX)
notPaused
returns (
uint // Returns the number of SNX received
)
{
return _exchangeSynthsForSNX(synthAmount);
}
/**
* @notice Exchange sUSD for SNX while insisting on a particular rate. This allows a user to
* exchange while protecting against frontrunning by the contract owner on the exchange rate.
* @param synthAmount The amount of synths the user wishes to exchange.
* @param guaranteedRate A rate (synthetix price) the caller wishes to insist upon.
*/
function exchangeSynthsForSNXAtRate(uint synthAmount, uint guaranteedRate)
external
rateNotInvalid(SNX)
notPaused
returns (
uint // Returns the number of SNX received
)
{
require(guaranteedRate == exchangeRates().rateForCurrency(SNX), "Guaranteed rate would not be received");
return _exchangeSynthsForSNX(synthAmount);
}
/**
* @notice Allows the owner to withdraw SNX from this contract if needed.
* @param amount The amount of SNX to attempt to withdraw (in 18 decimal places).
*/
function withdrawSynthetix(uint amount) external onlyOwner {
synthetix().transfer(owner, amount);
// We don't emit our own events here because we assume that anyone
// who wants to watch what the Depot is doing can
// just watch ERC20 events from the Synth and/or Synthetix contracts
// filtered to our address.
}
/**
* @notice Allows a user to withdraw all of their previously deposited synths from this contract if needed.
* Developer note: We could keep an index of address to deposits to make this operation more efficient
* but then all the other operations on the queue become less efficient. It's expected that this
* function will be very rarely used, so placing the inefficiency here is intentional. The usual
* use case does not involve a withdrawal.
*/
function withdrawMyDepositedSynths() external {
uint synthsToSend = 0;
for (uint i = depositStartIndex; i < depositEndIndex; i++) {
SynthDepositEntry memory deposit = deposits[i];
if (deposit.user == msg.sender) {
// The user is withdrawing this deposit. Remove it from our queue.
// We'll just leave a gap, which the purchasing logic can walk past.
synthsToSend = synthsToSend.add(deposit.amount);
delete deposits[i];
//Let the DApps know we've removed this deposit
emit SynthDepositRemoved(deposit.user, deposit.amount, i);
}
}
// Update our total
totalSellableDeposits = totalSellableDeposits.sub(synthsToSend);
// Check if the user has tried to send deposit amounts < the minimumDepositAmount to the FIFO
// queue which would have been added to this mapping for withdrawal only
synthsToSend = synthsToSend.add(smallDeposits[msg.sender]);
smallDeposits[msg.sender] = 0;
// If there's nothing to do then go ahead and revert the transaction
require(synthsToSend > 0, "You have no deposits to withdraw.");
// Send their deposits back to them (minus fees)
synthsUSD().transfer(msg.sender, synthsToSend);
emit SynthWithdrawal(msg.sender, synthsToSend);
}
/**
* @notice depositSynths: Allows users to deposit synths via the approve / transferFrom workflow
* @param amount The amount of sUSD you wish to deposit (must have been approved first)
*/
function depositSynths(uint amount) external {
// Grab the amount of synths. Will fail if not approved first
synthsUSD().transferFrom(msg.sender, address(this), amount);
// A minimum deposit amount is designed to protect purchasers from over paying
// gas for fullfilling multiple small synth deposits
if (amount < minimumDepositAmount) {
// We cant fail/revert the transaction or send the synths back in a reentrant call.
// So we will keep your synths balance seperate from the FIFO queue so you can withdraw them
smallDeposits[msg.sender] = smallDeposits[msg.sender].add(amount);
emit SynthDepositNotAccepted(msg.sender, amount, minimumDepositAmount);
} else {
// Ok, thanks for the deposit, let's queue it up.
deposits[depositEndIndex] = SynthDepositEntry({user: msg.sender, amount: amount});
emit SynthDeposit(msg.sender, amount, depositEndIndex);
// Walk our index forward as well.
depositEndIndex = depositEndIndex.add(1);
// And add it to our total.
totalSellableDeposits = totalSellableDeposits.add(amount);
}
}
/* ========== VIEWS ========== */
function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
addresses = new bytes32[](3);
addresses[0] = CONTRACT_SYNTHSUSD;
addresses[1] = CONTRACT_EXRATES;
addresses[2] = CONTRACT_SYNTHETIX;
}
/**
* @notice Calculate how many SNX you will receive if you transfer
* an amount of synths.
* @param amount The amount of synths (in 18 decimal places) you want to ask about
*/
function synthetixReceivedForSynths(uint amount) public view returns (uint) {
// And what would that be worth in SNX based on the current price?
return amount.divideDecimal(exchangeRates().rateForCurrency(SNX));
}
/**
* @notice Calculate how many SNX you will receive if you transfer
* an amount of ether.
* @param amount The amount of ether (in wei) you want to ask about
*/
function synthetixReceivedForEther(uint amount) public view returns (uint) {
// How much is the ETH they sent us worth in sUSD (ignoring the transfer fee)?
uint valueSentInSynths = amount.multiplyDecimal(exchangeRates().rateForCurrency(ETH));
// Now, how many SNX will that USD amount buy?
return synthetixReceivedForSynths(valueSentInSynths);
}
/**
* @notice Calculate how many synths you will receive if you transfer
* an amount of ether.
* @param amount The amount of ether (in wei) you want to ask about
*/
function synthsReceivedForEther(uint amount) public view returns (uint) {
// How many synths would that amount of ether be worth?
return amount.multiplyDecimal(exchangeRates().rateForCurrency(ETH));
}
/* ========== INTERNAL VIEWS ========== */
function synthsUSD() internal view returns (IERC20) {
return IERC20(requireAndGetAddress(CONTRACT_SYNTHSUSD));
}
function synthetix() internal view returns (IERC20) {
return IERC20(requireAndGetAddress(CONTRACT_SYNTHETIX));
}
function exchangeRates() internal view returns (IExchangeRates) {
return IExchangeRates(requireAndGetAddress(CONTRACT_EXRATES));
}
// ========== MODIFIERS ==========
modifier rateNotInvalid(bytes32 currencyKey) {
require(!exchangeRates().rateIsInvalid(currencyKey), "Rate invalid or not a synth");
_;
}
/* ========== EVENTS ========== */
event MaxEthPurchaseUpdated(uint amount);
event FundsWalletUpdated(address newFundsWallet);
event Exchange(string fromCurrency, uint fromAmount, string toCurrency, uint toAmount);
event SynthWithdrawal(address user, uint amount);
event SynthDeposit(address indexed user, uint amount, uint indexed depositIndex);
event SynthDepositRemoved(address indexed user, uint amount, uint indexed depositIndex);
event SynthDepositNotAccepted(address user, uint amount, uint minimum);
event MinimumDepositAmountUpdated(uint amount);
event NonPayableContract(address indexed receiver, uint amount);
event ClearedDeposit(
address indexed fromAddress,
address indexed toAddress,
uint fromETHAmount,
uint toAmount,
uint indexed depositIndex
);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
pragma experimental ABIEncoderV2;
// Inheritance
import "./Owned.sol";
import "./MixinResolver.sol";
import "./MixinSystemSettings.sol";
import "./IDirectIntegrationManager.sol";
/*
* SIP-267: Direct Integration
* https://sips.synthetix.io/sips/sip-267/
*
* Used by the Spartan Council to approve an external contract, (i.e. one which is not owned or managed by the Synthetix protocol),
* to interact with Synthetix's core exchange functionalities with overridden parameters.
* If no parameter overrides are specified, then the prevailing parameter configuration will be automatically used.
*/
contract DirectIntegrationManager is Owned, MixinSystemSettings, IDirectIntegrationManager {
/* ========== CONSTANTS ========== */
bytes32 private constant CONTRACT_NAME = "DirectIntegration";
bytes32 private constant CONTRACT_NAME_EXCHANGE_RATES = "ExchangeRates";
bytes32 internal constant SETTING_DEX_PRICE_AGGREGATOR = "dexPriceAggregator";
uint internal constant DI_VERSION = 1;
/* ---------- Internal Variables ---------- */
// Stores a mapping of all overridden parameters for a given direct integration.
mapping(address => mapping(bytes32 => ParameterIntegrationSettings)) internal _settings;
/* ========== CONSTRUCTOR ========== */
constructor(address _owner, address _resolver) public Owned(_owner) MixinSystemSettings(_resolver) {}
/* ========== VIEWS ========== */
/* ---------- Getters ---------- */
/**
* Used to read the configured overridden values for a given integration.
* @param integration the address of the external integrator's contract
*/
function getExchangeParameters(address integration, bytes32 currencyKey)
external
view
returns (ParameterIntegrationSettings memory overrides)
{
ParameterIntegrationSettings memory storedOverrides = _settings[integration][currencyKey];
return
ParameterIntegrationSettings({
currencyKey: currencyKey,
dexPriceAggregator: storedOverrides.dexPriceAggregator != address(0)
? storedOverrides.dexPriceAggregator
: flexibleStorage().getAddressValue(CONTRACT_NAME_EXCHANGE_RATES, SETTING_DEX_PRICE_AGGREGATOR),
atomicEquivalentForDexPricing: storedOverrides.atomicEquivalentForDexPricing != address(0)
? storedOverrides.atomicEquivalentForDexPricing
: getAtomicEquivalentForDexPricing(currencyKey),
atomicExchangeFeeRate: storedOverrides.atomicExchangeFeeRate > 0
? storedOverrides.atomicExchangeFeeRate
: getAtomicExchangeFeeRate(currencyKey),
atomicTwapWindow: storedOverrides.atomicTwapWindow > 0
? storedOverrides.atomicTwapWindow
: getAtomicTwapWindow(),
atomicMaxVolumePerBlock: storedOverrides.atomicMaxVolumePerBlock > 0
? storedOverrides.atomicMaxVolumePerBlock
: getAtomicMaxVolumePerBlock(),
atomicVolatilityConsiderationWindow: storedOverrides.atomicVolatilityConsiderationWindow > 0
? storedOverrides.atomicVolatilityConsiderationWindow
: getAtomicVolatilityConsiderationWindow(currencyKey),
atomicVolatilityUpdateThreshold: storedOverrides.atomicVolatilityUpdateThreshold > 0
? storedOverrides.atomicVolatilityUpdateThreshold
: getAtomicVolatilityUpdateThreshold(currencyKey),
exchangeFeeRate: storedOverrides.exchangeFeeRate > 0
? storedOverrides.exchangeFeeRate
: getExchangeFeeRate(currencyKey),
exchangeMaxDynamicFee: storedOverrides.exchangeMaxDynamicFee > 0
? storedOverrides.exchangeMaxDynamicFee
: getExchangeMaxDynamicFee(),
exchangeDynamicFeeRounds: storedOverrides.exchangeDynamicFeeRounds > 0
? storedOverrides.exchangeDynamicFeeRounds
: getExchangeDynamicFeeRounds(),
exchangeDynamicFeeThreshold: storedOverrides.exchangeDynamicFeeThreshold > 0
? storedOverrides.exchangeDynamicFeeThreshold
: getExchangeDynamicFeeThreshold(),
exchangeDynamicFeeWeightDecay: storedOverrides.exchangeDynamicFeeWeightDecay > 0
? storedOverrides.exchangeDynamicFeeWeightDecay
: getExchangeDynamicFeeWeightDecay()
});
}
/* ========== MUTATIVE FUNCTIONS ========== */
/**
* Sets an override to be used for a given direct integration that supersedes the default Synthetix parameter value.
* @param integration the address of the external integrator's contract
* @param settings a collection of parameters to be overridden
* @dev Invoking this function will overwrite whatever overrides were previously set. Set overrides to zero to "remove" them.
* @notice This will require a SIP and a presentation, given the importance of clearly presenting
* external interactions with Synthetix contracts and the parameter overrides that would be implemented.
*/
function setExchangeParameters(
address integration,
bytes32[] calldata currencyKeys,
ParameterIntegrationSettings calldata settings
) external onlyOwner {
for (uint i = 0; i < currencyKeys.length; i++) {
_setExchangeParameters(integration, currencyKeys[i], settings);
}
}
/* ---------- Internal Functions ---------- */
function _setExchangeParameters(
address integration,
bytes32 currencyKey,
ParameterIntegrationSettings memory settings
) internal {
require(address(integration) != address(0), "Address cannot be 0");
_settings[integration][currencyKey] = settings; // overwrites the parameters for a given direct integration
emit IntegrationParametersSet(integration, currencyKey, settings);
}
/* ========== EVENTS ========== */
event IntegrationParametersSet(
address indexed integration,
bytes32 indexed currencyKey,
ParameterIntegrationSettings overrides
);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
import "./ICollateralManager.sol";
contract EmptyCollateralManager is ICollateralManager {
// Manager information
function hasCollateral(address) external view returns (bool) {
return false;
}
function isSynthManaged(bytes32) external view returns (bool) {
return false;
}
// State information
function long(bytes32) external view returns (uint amount) {
return 0;
}
function short(bytes32) external view returns (uint amount) {
return 0;
}
function totalLong() external view returns (uint susdValue, bool anyRateIsInvalid) {
return (0, false);
}
function totalShort() external view returns (uint susdValue, bool anyRateIsInvalid) {
return (0, false);
}
function getBorrowRate() external view returns (uint borrowRate, bool anyRateIsInvalid) {
return (0, false);
}
function getShortRate(bytes32) external view returns (uint shortRate, bool rateIsInvalid) {
return (0, false);
}
function getRatesAndTime(uint)
external
view
returns (
uint entryRate,
uint lastRate,
uint lastUpdated,
uint newIndex
)
{
return (0, 0, 0, 0);
}
function getShortRatesAndTime(bytes32, uint)
external
view
returns (
uint entryRate,
uint lastRate,
uint lastUpdated,
uint newIndex
)
{
return (0, 0, 0, 0);
}
function exceedsDebtLimit(uint, bytes32) external view returns (bool canIssue, bool anyRateIsInvalid) {
return (false, false);
}
function areSynthsAndCurrenciesSet(bytes32[] calldata, bytes32[] calldata) external view returns (bool) {
return false;
}
function areShortableSynthsSet(bytes32[] calldata, bytes32[] calldata) external view returns (bool) {
return false;
}
// Loans
function getNewLoanId() external returns (uint id) {
return 0;
}
// Manager mutative
function addCollaterals(address[] calldata) external {}
function removeCollaterals(address[] calldata) external {}
function addSynths(bytes32[] calldata, bytes32[] calldata) external {}
function removeSynths(bytes32[] calldata, bytes32[] calldata) external {}
function addShortableSynths(bytes32[2][] calldata, bytes32[] calldata) external {}
function removeShortableSynths(bytes32[] calldata) external {}
// State mutative
function updateBorrowRates(uint) external {}
function updateShortRates(bytes32, uint) external {}
function incrementLongs(bytes32, uint) external {}
function decrementLongs(bytes32, uint) external {}
function incrementShorts(bytes32, uint) external {}
function decrementShorts(bytes32, uint) external {}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
// Stub functions required by the DebtCache and FeePool contracts.
// https://docs.synthetix.io/contracts/source/contracts/etherwrapper
contract EmptyEtherWrapper {
constructor() public {}
/* ========== VIEWS ========== */
function totalIssuedSynths() public view returns (uint) {
return 0;
}
function distributeFees() external {}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
// Empty contract for ether collateral placeholder for OVM
// https://docs.synthetix.io/contracts/source/contracts/emptyethercollateral
import "./IFuturesMarketManager.sol";
contract EmptyFuturesMarketManager is IFuturesMarketManager {
bytes32 public constant CONTRACT_NAME = "EmptyFuturesMarketManager";
function markets(uint index, uint pageSize) external view returns (address[] memory) {
index;
pageSize;
address[] memory _markets;
return _markets;
}
function markets(
uint index,
uint pageSize,
bool proxiedMarkets
) external view returns (address[] memory) {
index;
pageSize;
proxiedMarkets;
address[] memory _markets;
return _markets;
}
function numMarkets() external view returns (uint) {
return 0;
}
function numMarkets(bool proxiedMarkets) external view returns (uint) {
proxiedMarkets;
return 0;
}
function allMarkets() external view returns (address[] memory) {
address[] memory _markets;
return _markets;
}
function allMarkets(bool proxiedMarkets) external view returns (address[] memory) {
proxiedMarkets;
address[] memory _markets;
return _markets;
}
function marketForKey(bytes32 marketKey) external view returns (address) {
marketKey;
return address(0);
}
function marketsForKeys(bytes32[] calldata marketKeys) external view returns (address[] memory) {
marketKeys;
address[] memory _markets;
return _markets;
}
function totalDebt() external view returns (uint debt, bool isInvalid) {
return (0, false);
}
function isEndorsed(address account) external view returns (bool) {
account;
return false;
}
function allEndorsedAddresses() external view returns (address[] memory) {
address[] memory _endorsedAddresses;
return _endorsedAddresses;
}
function addEndorsedAddresses(address[] calldata addresses) external {
addresses;
}
function removeEndorsedAddresses(address[] calldata addresses) external {
addresses;
}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.0;
import "./IERC20.sol";
import "./SafeMath.sol";
/**
* @dev Implementation of the `IERC20` interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using `_mint`.
* For a generic mechanism see `ERC20Mintable`.
*
* *For a detailed writeup see our guide [How to implement supply
* mechanisms](https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226).*
*
* We have followed general OpenZeppelin guidelines: functions revert instead
* of returning `false` on failure. This behavior is nonetheless conventional
* and does not conflict with the expectations of ERC20 applications.
*
* Additionally, an `Approval` event is emitted on calls to `transferFrom`.
* This allows applications to reconstruct the allowance for all accounts just
* by listening to said events. Other implementations of the EIP may not emit
* these events, as it isn't required by the specification.
*
* Finally, the non-standard `decreaseAllowance` and `increaseAllowance`
* functions have been added to mitigate the well-known issues around setting
* allowances. See `IERC20.approve`.
*/
contract ERC20 is IERC20 {
using SafeMath for uint256;
mapping (address => uint256) private _balances;
mapping (address => mapping (address => uint256)) private _allowances;
uint256 private _totalSupply;
/**
* @dev See `IERC20.totalSupply`.
*/
function totalSupply() public view returns (uint256) {
return _totalSupply;
}
/**
* @dev See `IERC20.balanceOf`.
*/
function balanceOf(address account) public view returns (uint256) {
return _balances[account];
}
/**
* @dev See `IERC20.transfer`.
*
* Requirements:
*
* - `recipient` cannot be the zero address.
* - the caller must have a balance of at least `amount`.
*/
function transfer(address recipient, uint256 amount) public returns (bool) {
_transfer(msg.sender, recipient, amount);
return true;
}
/**
* @dev See `IERC20.allowance`.
*/
function allowance(address owner, address spender) public view returns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See `IERC20.approve`.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 value) public returns (bool) {
_approve(msg.sender, spender, value);
return true;
}
/**
* @dev See `IERC20.transferFrom`.
*
* Emits an `Approval` event indicating the updated allowance. This is not
* required by the EIP. See the note at the beginning of `ERC20`;
*
* Requirements:
* - `sender` and `recipient` cannot be the zero address.
* - `sender` must have a balance of at least `value`.
* - the caller must have allowance for `sender`'s tokens of at least
* `amount`.
*/
function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
_transfer(sender, recipient, amount);
_approve(sender, msg.sender, _allowances[sender][msg.sender].sub(amount));
return true;
}
/**
* @dev Atomically increases the allowance granted to `spender` by the caller.
*
* This is an alternative to `approve` that can be used as a mitigation for
* problems described in `IERC20.approve`.
*
* Emits an `Approval` event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
_approve(msg.sender, spender, _allowances[msg.sender][spender].add(addedValue));
return true;
}
/**
* @dev Atomically decreases the allowance granted to `spender` by the caller.
*
* This is an alternative to `approve` that can be used as a mitigation for
* problems described in `IERC20.approve`.
*
* Emits an `Approval` event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `spender` must have allowance for the caller of at least
* `subtractedValue`.
*/
function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
_approve(msg.sender, spender, _allowances[msg.sender][spender].sub(subtractedValue));
return true;
}
/**
* @dev Moves tokens `amount` from `sender` to `recipient`.
*
* This is internal function is equivalent to `transfer`, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a `Transfer` event.
*
* Requirements:
*
* - `sender` cannot be the zero address.
* - `recipient` cannot be the zero address.
* - `sender` must have a balance of at least `amount`.
*/
function _transfer(address sender, address recipient, uint256 amount) internal {
require(sender != address(0), "ERC20: transfer from the zero address");
require(recipient != address(0), "ERC20: transfer to the zero address");
_balances[sender] = _balances[sender].sub(amount);
_balances[recipient] = _balances[recipient].add(amount);
emit Transfer(sender, recipient, amount);
}
/** @dev Creates `amount` tokens and assigns them to `account`, increasing
* the total supply.
*
* Emits a `Transfer` event with `from` set to the zero address.
*
* Requirements
*
* - `to` cannot be the zero address.
*/
function _mint(address account, uint256 amount) internal {
require(account != address(0), "ERC20: mint to the zero address");
_totalSupply = _totalSupply.add(amount);
_balances[account] = _balances[account].add(amount);
emit Transfer(address(0), account, amount);
}
/**
* @dev Destoys `amount` tokens from `account`, reducing the
* total supply.
*
* Emits a `Transfer` event with `to` set to the zero address.
*
* Requirements
*
* - `account` cannot be the zero address.
* - `account` must have at least `amount` tokens.
*/
function _burn(address account, uint256 value) internal {
require(account != address(0), "ERC20: burn from the zero address");
_totalSupply = _totalSupply.sub(value);
_balances[account] = _balances[account].sub(value);
emit Transfer(account, address(0), value);
}
/**
* @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens.
*
* This is internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an `Approval` event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*/
function _approve(address owner, address spender, uint256 value) internal {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = value;
emit Approval(owner, spender, value);
}
/**
* @dev Destoys `amount` tokens from `account`.`amount` is then deducted
* from the caller's allowance.
*
* See `_burn` and `_approve`.
*/
function _burnFrom(address account, uint256 amount) internal {
_burn(account, amount);
_approve(account, msg.sender, _allowances[account][msg.sender].sub(amount));
}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.0;
import "./IERC20.sol";
/**
* @dev Optional functions from the ERC20 standard.
*/
contract ERC20Detailed is IERC20 {
string private _name;
string private _symbol;
uint8 private _decimals;
/**
* @dev Sets the values for `name`, `symbol`, and `decimals`. All three of
* these values are immutable: they can only be set once during
* construction.
*/
constructor (string memory name, string memory symbol, uint8 decimals) public {
_name = name;
_symbol = symbol;
_decimals = decimals;
}
/**
* @dev Returns the name of the token.
*/
function name() public view returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5,05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei.
*
* > Note that this information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* `IERC20.balanceOf` and `IERC20.transfer`.
*/
function decimals() public view returns (uint8) {
return _decimals;
}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
interface ISynthetixEscrow {
function numVestingEntries(address account) external view returns (uint);
function getVestingScheduleEntry(address account, uint index) external view returns (uint[2] memory);
}
// https://docs.synthetix.io/contracts/source/contracts/escrowchecker
contract EscrowChecker {
ISynthetixEscrow public synthetix_escrow;
constructor(ISynthetixEscrow _esc) public {
synthetix_escrow = _esc;
}
function checkAccountSchedule(address account) public view returns (uint[16] memory) {
uint[16] memory _result;
uint schedules = synthetix_escrow.numVestingEntries(account);
for (uint i = 0; i < schedules; i++) {
uint[2] memory pair = synthetix_escrow.getVestingScheduleEntry(account, i);
_result[i * 2] = pair[0];
_result[i * 2 + 1] = pair[1];
}
return _result;
}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
// Inheritance
import "./Owned.sol";
import "./State.sol";
// https://docs.synthetix.io/contracts/source/contracts/eternalstorage
/**
* @notice This contract is based on the code available from this blog
* https://blog.colony.io/writing-upgradeable-contracts-in-solidity-6743f0eecc88/
* Implements support for storing a keccak256 key and value pairs. It is the more flexible
* and extensible option. This ensures data schema changes can be implemented without
* requiring upgrades to the storage contract.
*/
contract EternalStorage is Owned, State {
constructor(address _owner, address _associatedContract) public Owned(_owner) State(_associatedContract) {}
/* ========== DATA TYPES ========== */
mapping(bytes32 => uint) internal UIntStorage;
mapping(bytes32 => string) internal StringStorage;
mapping(bytes32 => address) internal AddressStorage;
mapping(bytes32 => bytes) internal BytesStorage;
mapping(bytes32 => bytes32) internal Bytes32Storage;
mapping(bytes32 => bool) internal BooleanStorage;
mapping(bytes32 => int) internal IntStorage;
// UIntStorage;
function getUIntValue(bytes32 record) external view returns (uint) {
return UIntStorage[record];
}
function setUIntValue(bytes32 record, uint value) external onlyAssociatedContract {
UIntStorage[record] = value;
}
function deleteUIntValue(bytes32 record) external onlyAssociatedContract {
delete UIntStorage[record];
}
// StringStorage
function getStringValue(bytes32 record) external view returns (string memory) {
return StringStorage[record];
}
function setStringValue(bytes32 record, string calldata value) external onlyAssociatedContract {
StringStorage[record] = value;
}
function deleteStringValue(bytes32 record) external onlyAssociatedContract {
delete StringStorage[record];
}
// AddressStorage
function getAddressValue(bytes32 record) external view returns (address) {
return AddressStorage[record];
}
function setAddressValue(bytes32 record, address value) external onlyAssociatedContract {
AddressStorage[record] = value;
}
function deleteAddressValue(bytes32 record) external onlyAssociatedContract {
delete AddressStorage[record];
}
// BytesStorage
function getBytesValue(bytes32 record) external view returns (bytes memory) {
return BytesStorage[record];
}
function setBytesValue(bytes32 record, bytes calldata value) external onlyAssociatedContract {
BytesStorage[record] = value;
}
function deleteBytesValue(bytes32 record) external onlyAssociatedContract {
delete BytesStorage[record];
}
// Bytes32Storage
function getBytes32Value(bytes32 record) external view returns (bytes32) {
return Bytes32Storage[record];
}
function setBytes32Value(bytes32 record, bytes32 value) external onlyAssociatedContract {
Bytes32Storage[record] = value;
}
function deleteBytes32Value(bytes32 record) external onlyAssociatedContract {
delete Bytes32Storage[record];
}
// BooleanStorage
function getBooleanValue(bytes32 record) external view returns (bool) {
return BooleanStorage[record];
}
function setBooleanValue(bytes32 record, bool value) external onlyAssociatedContract {
BooleanStorage[record] = value;
}
function deleteBooleanValue(bytes32 record) external onlyAssociatedContract {
delete BooleanStorage[record];
}
// IntStorage
function getIntValue(bytes32 record) external view returns (int) {
return IntStorage[record];
}
function setIntValue(bytes32 record, int value) external onlyAssociatedContract {
IntStorage[record] = value;
}
function deleteIntValue(bytes32 record) external onlyAssociatedContract {
delete IntStorage[record];
}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
// Inheritance
import "./Owned.sol";
import "./IAddressResolver.sol";
import "./IEtherWrapper.sol";
import "./ISynth.sol";
import "./IERC20.sol";
import "./IWETH.sol";
// Internal references
import "./Pausable.sol";
import "./IIssuer.sol";
import "./IExchangeRates.sol";
import "./IFeePool.sol";
import "./MixinResolver.sol";
import "./MixinSystemSettings.sol";
// Libraries
import "./SafeMath.sol";
import "./SafeDecimalMath.sol";
// https://docs.synthetix.io/contracts/source/contracts/etherwrapper
contract EtherWrapper is Owned, Pausable, MixinResolver, MixinSystemSettings, IEtherWrapper {
using SafeMath for uint;
using SafeDecimalMath for uint;
/* ========== CONSTANTS ============== */
/* ========== ENCODED NAMES ========== */
bytes32 internal constant sUSD = "sUSD";
bytes32 internal constant sETH = "sETH";
bytes32 internal constant ETH = "ETH";
bytes32 internal constant SNX = "SNX";
/* ========== ADDRESS RESOLVER CONFIGURATION ========== */
bytes32 private constant CONTRACT_SYNTHSETH = "SynthsETH";
bytes32 private constant CONTRACT_SYNTHSUSD = "SynthsUSD";
bytes32 private constant CONTRACT_ISSUER = "Issuer";
bytes32 private constant CONTRACT_EXRATES = "ExchangeRates";
bytes32 private constant CONTRACT_FEEPOOL = "FeePool";
// ========== STATE VARIABLES ==========
IWETH internal _weth;
uint public sETHIssued = 0;
uint public sUSDIssued = 0;
uint public feesEscrowed = 0;
constructor(
address _owner,
address _resolver,
address payable _WETH
) public Owned(_owner) Pausable() MixinSystemSettings(_resolver) {
_weth = IWETH(_WETH);
}
/* ========== VIEWS ========== */
function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
bytes32[] memory existingAddresses = MixinSystemSettings.resolverAddressesRequired();
bytes32[] memory newAddresses = new bytes32[](5);
newAddresses[0] = CONTRACT_SYNTHSETH;
newAddresses[1] = CONTRACT_SYNTHSUSD;
newAddresses[2] = CONTRACT_EXRATES;
newAddresses[3] = CONTRACT_ISSUER;
newAddresses[4] = CONTRACT_FEEPOOL;
addresses = combineArrays(existingAddresses, newAddresses);
return addresses;
}
/* ========== INTERNAL VIEWS ========== */
function synthsUSD() internal view returns (ISynth) {
return ISynth(requireAndGetAddress(CONTRACT_SYNTHSUSD));
}
function synthsETH() internal view returns (ISynth) {
return ISynth(requireAndGetAddress(CONTRACT_SYNTHSETH));
}
function feePool() internal view returns (IFeePool) {
return IFeePool(requireAndGetAddress(CONTRACT_FEEPOOL));
}
function exchangeRates() internal view returns (IExchangeRates) {
return IExchangeRates(requireAndGetAddress(CONTRACT_EXRATES));
}
function issuer() internal view returns (IIssuer) {
return IIssuer(requireAndGetAddress(CONTRACT_ISSUER));
}
/* ========== PUBLIC FUNCTIONS ========== */
// ========== VIEWS ==========
function capacity() public view returns (uint _capacity) {
// capacity = max(maxETH - balance, 0)
uint balance = getReserves();
if (balance >= maxETH()) {
return 0;
}
return maxETH().sub(balance);
}
function getReserves() public view returns (uint) {
return _weth.balanceOf(address(this));
}
function totalIssuedSynths() public view returns (uint) {
// This contract issues two different synths:
// 1. sETH
// 2. sUSD
//
// The sETH is always backed 1:1 with WETH.
// The sUSD fees are backed by sETH that is withheld during minting and burning.
return exchangeRates().effectiveValue(sETH, sETHIssued, sUSD).add(sUSDIssued);
}
function calculateMintFee(uint amount) public view returns (uint) {
return amount.multiplyDecimalRound(mintFeeRate());
}
function calculateBurnFee(uint amount) public view returns (uint) {
return amount.multiplyDecimalRound(burnFeeRate());
}
function maxETH() public view returns (uint256) {
return getEtherWrapperMaxETH();
}
function mintFeeRate() public view returns (uint256) {
return getEtherWrapperMintFeeRate();
}
function burnFeeRate() public view returns (uint256) {
return getEtherWrapperBurnFeeRate();
}
function weth() public view returns (IWETH) {
return _weth;
}
/* ========== MUTATIVE FUNCTIONS ========== */
// Transfers `amountIn` WETH to mint `amountIn - fees` sETH.
// `amountIn` is inclusive of fees, calculable via `calculateMintFee`.
function mint(uint amountIn) external notPaused {
require(amountIn <= _weth.allowance(msg.sender, address(this)), "Allowance not high enough");
require(amountIn <= _weth.balanceOf(msg.sender), "Balance is too low");
uint currentCapacity = capacity();
require(currentCapacity > 0, "Contract has no spare capacity to mint");
if (amountIn < currentCapacity) {
_mint(amountIn);
} else {
_mint(currentCapacity);
}
}
// Burns `amountIn` sETH for `amountIn - fees` WETH.
// `amountIn` is inclusive of fees, calculable via `calculateBurnFee`.
function burn(uint amountIn) external notPaused {
uint reserves = getReserves();
require(reserves > 0, "Contract cannot burn sETH for WETH, WETH balance is zero");
// principal = [amountIn / (1 + burnFeeRate)]
uint principal = amountIn.divideDecimalRound(SafeDecimalMath.unit().add(burnFeeRate()));
if (principal < reserves) {
_burn(principal, amountIn);
} else {
_burn(reserves, reserves.add(calculateBurnFee(reserves)));
}
}
function distributeFees() external {
// Normalize fee to sUSD
require(!exchangeRates().rateIsInvalid(sETH), "Currency rate is invalid");
uint amountSUSD = exchangeRates().effectiveValue(sETH, feesEscrowed, sUSD);
// Burn sETH.
synthsETH().burn(address(this), feesEscrowed);
// Pay down as much sETH debt as we burn. Any other debt is taken on by the stakers.
sETHIssued = sETHIssued < feesEscrowed ? 0 : sETHIssued.sub(feesEscrowed);
// Issue sUSD to the fee pool
issuer().synths(sUSD).issue(feePool().FEE_ADDRESS(), amountSUSD);
sUSDIssued = sUSDIssued.add(amountSUSD);
// Tell the fee pool about this
feePool().recordFeePaid(amountSUSD);
feesEscrowed = 0;
}
// ========== RESTRICTED ==========
/**
* @notice Fallback function
*/
function() external payable {
revert("Fallback disabled, use mint()");
}
/* ========== INTERNAL FUNCTIONS ========== */
function _mint(uint amountIn) internal {
// Calculate minting fee.
uint feeAmountEth = calculateMintFee(amountIn);
uint principal = amountIn.sub(feeAmountEth);
// Transfer WETH from user.
_weth.transferFrom(msg.sender, address(this), amountIn);
// Mint `amountIn - fees` sETH to user.
synthsETH().issue(msg.sender, principal);
// Escrow fee.
synthsETH().issue(address(this), feeAmountEth);
feesEscrowed = feesEscrowed.add(feeAmountEth);
// Add sETH debt.
sETHIssued = sETHIssued.add(amountIn);
emit Minted(msg.sender, principal, feeAmountEth, amountIn);
}
function _burn(uint principal, uint amountIn) internal {
// for burn, amount is inclusive of the fee.
uint feeAmountEth = amountIn.sub(principal);
require(amountIn <= IERC20(address(synthsETH())).allowance(msg.sender, address(this)), "Allowance not high enough");
require(amountIn <= IERC20(address(synthsETH())).balanceOf(msg.sender), "Balance is too low");
// Burn `amountIn` sETH from user.
synthsETH().burn(msg.sender, amountIn);
// sETH debt is repaid by burning.
sETHIssued = sETHIssued < principal ? 0 : sETHIssued.sub(principal);
// We use burn/issue instead of burning the principal and transferring the fee.
// This saves an approval and is cheaper.
// Escrow fee.
synthsETH().issue(address(this), feeAmountEth);
// We don't update sETHIssued, as only the principal was subtracted earlier.
feesEscrowed = feesEscrowed.add(feeAmountEth);
// Transfer `amount - fees` WETH to user.
_weth.transfer(msg.sender, principal);
emit Burned(msg.sender, principal, feeAmountEth, amountIn);
}
/* ========== EVENTS ========== */
event Minted(address indexed account, uint principal, uint fee, uint amountIn);
event Burned(address indexed account, uint principal, uint fee, uint amountIn);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
// Inheritance
import "./Owned.sol";
import "./MixinResolver.sol";
import "./MixinSystemSettings.sol";
import "./IExchangeCircuitBreaker.sol";
// Libraries
import "./SafeDecimalMath.sol";
// Internal references
import "./ISynth.sol";
import "./IIssuer.sol";
import "./ISystemStatus.sol";
import "./IExchangeRates.sol";
import "./Proxyable.sol";
/**
* Compares current exchange rate to previous, and suspends a synth if the
* difference is outside of deviation bounds.
*
* This contract's functionality has been superseded by `CircuitBreaker`, and therefore its *deprecated*.
* ExchangeCircuitBreaker is currently used within the system only as a compatibility measure for non-upgradable
* contracts for the time being.
*
* https://docs.synthetix.io/contracts/source/contracts/ExchangeCircuitBreaker
*/
contract ExchangeCircuitBreaker is Owned, MixinSystemSettings, IExchangeCircuitBreaker {
using SafeMath for uint;
using SafeDecimalMath for uint;
bytes32 public constant CONTRACT_NAME = "ExchangeCircuitBreaker";
// SIP-65: Decentralized circuit breaker
uint public constant CIRCUIT_BREAKER_SUSPENSION_REASON = 65;
/* ========== ADDRESS RESOLVER CONFIGURATION ========== */
bytes32 private constant CONTRACT_EXRATES = "ExchangeRates";
constructor(address _owner, address _resolver) public Owned(_owner) MixinSystemSettings(_resolver) {}
/* ========== VIEWS ========== */
function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
bytes32[] memory existingAddresses = MixinSystemSettings.resolverAddressesRequired();
bytes32[] memory newAddresses = new bytes32[](1);
newAddresses[0] = CONTRACT_EXRATES;
addresses = combineArrays(existingAddresses, newAddresses);
}
function exchangeRates() public view returns (IExchangeRates) {
return IExchangeRates(requireAndGetAddress(CONTRACT_EXRATES));
}
// Returns rate and its "invalid" state.
// Rate can be invalid either due to:
// 1. Returned as invalid from ExchangeRates - due to being stale, or flagged by oracle.
// 2, Out of deviation dounds w.r.t. to previously stored rate or if there is no
// valid stored rate, w.r.t. to previous 3 oracle rates.
function rateWithInvalid(bytes32 currencyKey) external view returns (uint rate, bool invalid) {
(rate, invalid) = exchangeRates().rateAndInvalid(currencyKey);
}
/* ========== Mutating ========== */
/**
* COMPATIBILITY -- calls `ExchangeRates.rateWithSafetyChecks` which provides equivalent functionality for non-upgradable
* contracts (futures)
*/
function rateWithBreakCircuit(bytes32 currencyKey) external returns (uint lastValidRate, bool invalid) {
bool staleOrInvalid;
bool circuitBroken;
(lastValidRate, circuitBroken, staleOrInvalid) = exchangeRates().rateWithSafetyChecks(currencyKey);
invalid = circuitBroken || staleOrInvalid;
}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
pragma experimental ABIEncoderV2;
// Inheritance
import "./Owned.sol";
import "./MixinResolver.sol";
import "./MixinSystemSettings.sol";
// Libraries
import "./SafeDecimalMath.sol";
// Internal references
import "./ISystemStatus.sol";
import "./IERC20.sol";
import "./ICircuitBreaker.sol";
import "./IFeePool.sol";
import "./IDelegateApprovals.sol";
import "./ITradingRewards.sol";
import "./IVirtualSynth.sol";
import "./ExchangeSettlementLib.sol";
import "./Proxyable.sol";
// https://docs.synthetix.io/contracts/source/contracts/exchanger
contract Exchanger is Owned, MixinSystemSettings, IExchanger {
using SafeMath for uint;
using SafeDecimalMath for uint;
bytes32 public constant CONTRACT_NAME = "Exchanger";
bytes32 internal constant sUSD = "sUSD";
/* ========== ADDRESS RESOLVER CONFIGURATION ========== */
bytes32 private constant CONTRACT_SYSTEMSTATUS = "SystemStatus";
bytes32 private constant CONTRACT_EXCHANGESTATE = "ExchangeState";
bytes32 private constant CONTRACT_EXRATES = "ExchangeRates";
bytes32 private constant CONTRACT_SYNTHETIX = "Synthetix";
bytes32 private constant CONTRACT_FEEPOOL = "FeePool";
bytes32 private constant CONTRACT_TRADING_REWARDS = "TradingRewards";
bytes32 private constant CONTRACT_DELEGATEAPPROVALS = "DelegateApprovals";
bytes32 private constant CONTRACT_ISSUER = "Issuer";
bytes32 private constant CONTRACT_DEBTCACHE = "DebtCache";
bytes32 private constant CONTRACT_CIRCUIT_BREAKER = "CircuitBreaker";
bytes32 private constant CONTRACT_DIRECT_INTEGRATION_MANAGER = "DirectIntegrationManager";
constructor(address _owner, address _resolver) public Owned(_owner) MixinSystemSettings(_resolver) {}
/* ========== VIEWS ========== */
function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
bytes32[] memory existingAddresses = MixinSystemSettings.resolverAddressesRequired();
bytes32[] memory newAddresses = new bytes32[](11);
newAddresses[0] = CONTRACT_SYSTEMSTATUS;
newAddresses[1] = CONTRACT_EXCHANGESTATE;
newAddresses[2] = CONTRACT_EXRATES;
newAddresses[3] = CONTRACT_SYNTHETIX;
newAddresses[4] = CONTRACT_FEEPOOL;
newAddresses[5] = CONTRACT_TRADING_REWARDS;
newAddresses[6] = CONTRACT_DELEGATEAPPROVALS;
newAddresses[7] = CONTRACT_ISSUER;
newAddresses[8] = CONTRACT_DEBTCACHE;
newAddresses[9] = CONTRACT_CIRCUIT_BREAKER;
newAddresses[10] = CONTRACT_DIRECT_INTEGRATION_MANAGER;
addresses = combineArrays(existingAddresses, newAddresses);
}
function systemStatus() internal view returns (ISystemStatus) {
return ISystemStatus(requireAndGetAddress(CONTRACT_SYSTEMSTATUS));
}
function exchangeState() internal view returns (IExchangeState) {
return IExchangeState(requireAndGetAddress(CONTRACT_EXCHANGESTATE));
}
function exchangeRates() internal view returns (IExchangeRates) {
return IExchangeRates(requireAndGetAddress(CONTRACT_EXRATES));
}
function circuitBreaker() internal view returns (ICircuitBreaker) {
return ICircuitBreaker(requireAndGetAddress(CONTRACT_CIRCUIT_BREAKER));
}
function synthetix() internal view returns (ISynthetix) {
return ISynthetix(requireAndGetAddress(CONTRACT_SYNTHETIX));
}
function feePool() internal view returns (IFeePool) {
return IFeePool(requireAndGetAddress(CONTRACT_FEEPOOL));
}
function tradingRewards() internal view returns (ITradingRewards) {
return ITradingRewards(requireAndGetAddress(CONTRACT_TRADING_REWARDS));
}
function delegateApprovals() internal view returns (IDelegateApprovals) {
return IDelegateApprovals(requireAndGetAddress(CONTRACT_DELEGATEAPPROVALS));
}
function issuer() internal view returns (IIssuer) {
return IIssuer(requireAndGetAddress(CONTRACT_ISSUER));
}
function debtCache() internal view returns (IExchangerInternalDebtCache) {
return IExchangerInternalDebtCache(requireAndGetAddress(CONTRACT_DEBTCACHE));
}
function directIntegrationManager() internal view returns (IDirectIntegrationManager) {
return IDirectIntegrationManager(requireAndGetAddress(CONTRACT_DIRECT_INTEGRATION_MANAGER));
}
function resolvedAddresses() internal view returns (ExchangeSettlementLib.ResolvedAddresses memory) {
return
ExchangeSettlementLib.ResolvedAddresses(
exchangeState(),
exchangeRates(),
circuitBreaker(),
debtCache(),
issuer(),
synthetix()
);
}
function waitingPeriodSecs() external view returns (uint) {
return getWaitingPeriodSecs();
}
function tradingRewardsEnabled() external view returns (bool) {
return getTradingRewardsEnabled();
}
function priceDeviationThresholdFactor() external view returns (uint) {
return getPriceDeviationThresholdFactor();
}
function lastExchangeRate(bytes32 currencyKey) external view returns (uint) {
return circuitBreaker().lastValue(address(exchangeRates().aggregators(currencyKey)));
}
function settlementOwing(address account, bytes32 currencyKey)
public
view
returns (
uint reclaimAmount,
uint rebateAmount,
uint numEntries
)
{
(reclaimAmount, rebateAmount, numEntries, ) = ExchangeSettlementLib.settlementOwing(
resolvedAddresses(),
account,
currencyKey,
getWaitingPeriodSecs()
);
}
function hasWaitingPeriodOrSettlementOwing(address account, bytes32 currencyKey) external view returns (bool) {
return
ExchangeSettlementLib.hasWaitingPeriodOrSettlementOwing(
resolvedAddresses(),
account,
currencyKey,
getWaitingPeriodSecs()
);
}
function maxSecsLeftInWaitingPeriod(address account, bytes32 currencyKey) public view returns (uint) {
return
ExchangeSettlementLib._secsLeftInWaitingPeriodForExchange(
exchangeState().getMaxTimestamp(account, currencyKey),
getWaitingPeriodSecs()
);
}
/* ========== SETTERS ========== */
function calculateAmountAfterSettlement(
address from,
bytes32 currencyKey,
uint amount,
uint refunded
) public view returns (uint amountAfterSettlement) {
amountAfterSettlement = amount;
// balance of a synth will show an amount after settlement
uint balanceOfSourceAfterSettlement = IERC20(address(issuer().synths(currencyKey))).balanceOf(from);
// when there isn't enough supply (either due to reclamation settlement or because the number is too high)
if (amountAfterSettlement > balanceOfSourceAfterSettlement) {
// then the amount to exchange is reduced to their remaining supply
amountAfterSettlement = balanceOfSourceAfterSettlement;
}
if (refunded > 0) {
amountAfterSettlement = amountAfterSettlement.add(refunded);
}
}
function isSynthRateInvalid(bytes32 currencyKey) external view returns (bool) {
(, bool invalid) = exchangeRates().rateAndInvalid(currencyKey);
return invalid;
}
/* ========== MUTATIVE FUNCTIONS ========== */
function exchange(
address exchangeForAddress,
address from,
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey,
address destinationAddress,
bool virtualSynth,
address rewardAddress,
bytes32 trackingCode
) external onlySynthetixorSynth returns (uint amountReceived, IVirtualSynth vSynth) {
uint fee;
if (from != exchangeForAddress) {
require(delegateApprovals().canExchangeFor(exchangeForAddress, from), "Not approved to act on behalf");
}
IDirectIntegrationManager.ParameterIntegrationSettings memory sourceSettings =
_exchangeSettings(from, sourceCurrencyKey);
IDirectIntegrationManager.ParameterIntegrationSettings memory destinationSettings =
_exchangeSettings(from, destinationCurrencyKey);
(amountReceived, fee, vSynth) = _exchange(
exchangeForAddress,
sourceSettings,
sourceAmount,
destinationSettings,
destinationAddress,
virtualSynth
);
_processTradingRewards(fee, rewardAddress);
if (trackingCode != bytes32(0)) {
_emitTrackingEvent(trackingCode, destinationCurrencyKey, amountReceived, fee);
}
}
function exchangeAtomically(
address,
bytes32,
uint,
bytes32,
address,
bytes32,
uint
) external returns (uint) {
_notImplemented();
}
function _emitTrackingEvent(
bytes32 trackingCode,
bytes32 toCurrencyKey,
uint256 toAmount,
uint256 fee
) internal {
ISynthetixInternal(address(synthetix())).emitExchangeTracking(trackingCode, toCurrencyKey, toAmount, fee);
}
function _processTradingRewards(uint fee, address rewardAddress) internal {
if (fee > 0 && rewardAddress != address(0) && getTradingRewardsEnabled()) {
tradingRewards().recordExchangeFeeForAccount(fee, rewardAddress);
}
}
function _updateSNXIssuedDebtOnExchange(bytes32[2] memory currencyKeys, uint[2] memory currencyRates) internal {
bool includesSUSD = currencyKeys[0] == sUSD || currencyKeys[1] == sUSD;
uint numKeys = includesSUSD ? 2 : 3;
bytes32[] memory keys = new bytes32[](numKeys);
keys[0] = currencyKeys[0];
keys[1] = currencyKeys[1];
uint[] memory rates = new uint[](numKeys);
rates[0] = currencyRates[0];
rates[1] = currencyRates[1];
if (!includesSUSD) {
keys[2] = sUSD; // And we'll also update sUSD to account for any fees if it wasn't one of the exchanged currencies
rates[2] = SafeDecimalMath.unit();
}
// Note that exchanges can't invalidate the debt cache, since if a rate is invalid,
// the exchange will have failed already.
debtCache().updateCachedSynthDebtsWithRates(keys, rates);
}
function _settleAndCalcSourceAmountRemaining(
uint sourceAmount,
address from,
bytes32 sourceCurrencyKey
) internal returns (uint sourceAmountAfterSettlement) {
(, uint refunded, uint numEntriesSettled) =
ExchangeSettlementLib.internalSettle(
resolvedAddresses(),
from,
sourceCurrencyKey,
false,
getWaitingPeriodSecs()
);
sourceAmountAfterSettlement = sourceAmount;
// when settlement was required
if (numEntriesSettled > 0) {
// ensure the sourceAmount takes this into account
sourceAmountAfterSettlement = calculateAmountAfterSettlement(from, sourceCurrencyKey, sourceAmount, refunded);
}
}
function _exchange(
address from,
IDirectIntegrationManager.ParameterIntegrationSettings memory sourceSettings,
uint sourceAmount,
IDirectIntegrationManager.ParameterIntegrationSettings memory destinationSettings,
address destinationAddress,
bool virtualSynth
)
internal
returns (
uint amountReceived,
uint fee,
IVirtualSynth vSynth
)
{
if (!_ensureCanExchange(sourceSettings.currencyKey, destinationSettings.currencyKey, sourceAmount)) {
return (0, 0, IVirtualSynth(0));
}
// Using struct to resolve stack too deep error
IExchanger.ExchangeEntry memory entry;
ExchangeSettlementLib.ResolvedAddresses memory addrs = resolvedAddresses();
entry.roundIdForSrc = addrs.exchangeRates.getCurrentRoundId(sourceSettings.currencyKey);
entry.roundIdForDest = addrs.exchangeRates.getCurrentRoundId(destinationSettings.currencyKey);
entry.sourceAmountAfterSettlement = _settleAndCalcSourceAmountRemaining(
sourceAmount,
from,
sourceSettings.currencyKey
);
// If, after settlement the user has no balance left (highly unlikely), then return to prevent
// emitting events of 0 and don't revert so as to ensure the settlement queue is emptied
if (entry.sourceAmountAfterSettlement == 0) {
return (0, 0, IVirtualSynth(0));
}
(entry.destinationAmount, entry.sourceRate, entry.destinationRate) = addrs
.exchangeRates
.effectiveValueAndRatesAtRound(
sourceSettings.currencyKey,
entry.sourceAmountAfterSettlement,
destinationSettings.currencyKey,
entry.roundIdForSrc,
entry.roundIdForDest
);
// rates must also be good for the round we are doing
_ensureCanExchangeAtRound(
sourceSettings.currencyKey,
destinationSettings.currencyKey,
entry.roundIdForSrc,
entry.roundIdForDest
);
bool tooVolatile;
(entry.exchangeFeeRate, tooVolatile) = _feeRateForExchangeAtRounds(
sourceSettings,
destinationSettings,
entry.roundIdForSrc,
entry.roundIdForDest
);
if (tooVolatile) {
// do not exchange if rates are too volatile, this to prevent charging
// dynamic fees that are over the max value
return (0, 0, IVirtualSynth(0));
}
amountReceived = ExchangeSettlementLib._deductFeesFromAmount(entry.destinationAmount, entry.exchangeFeeRate);
// Note: `fee` is denominated in the destinationCurrencyKey.
fee = entry.destinationAmount.sub(amountReceived);
// Note: We don't need to check their balance as the _convert() below will do a safe subtraction which requires
// the subtraction to not overflow, which would happen if their balance is not sufficient.
vSynth = _convert(
sourceSettings.currencyKey,
from,
entry.sourceAmountAfterSettlement,
destinationSettings.currencyKey,
amountReceived,
destinationAddress,
virtualSynth
);
// When using a virtual synth, it becomes the destinationAddress for event and settlement tracking
if (vSynth != IVirtualSynth(0)) {
destinationAddress = address(vSynth);
}
// Remit the fee if required
if (fee > 0) {
// Normalize fee to sUSD
// Note: `fee` is being reused to avoid stack too deep errors.
fee = addrs.exchangeRates.effectiveValue(destinationSettings.currencyKey, fee, sUSD);
// Remit the fee in sUSDs
issuer().synths(sUSD).issue(feePool().FEE_ADDRESS(), fee);
// Tell the fee pool about this
feePool().recordFeePaid(fee);
}
// Note: As of this point, `fee` is denominated in sUSD.
// Nothing changes as far as issuance data goes because the total value in the system hasn't changed.
// But we will update the debt snapshot in case exchange rates have fluctuated since the last exchange
// in these currencies
_updateSNXIssuedDebtOnExchange(
[sourceSettings.currencyKey, destinationSettings.currencyKey],
[entry.sourceRate, entry.destinationRate]
);
// Let the DApps know there was a Synth exchange
ISynthetixInternal(address(synthetix())).emitSynthExchange(
from,
sourceSettings.currencyKey,
entry.sourceAmountAfterSettlement,
destinationSettings.currencyKey,
amountReceived,
destinationAddress
);
// iff the waiting period is gt 0
if (getWaitingPeriodSecs() > 0) {
// persist the exchange information for the dest key
ExchangeSettlementLib.appendExchange(
addrs,
destinationAddress,
sourceSettings.currencyKey,
entry.sourceAmountAfterSettlement,
destinationSettings.currencyKey,
amountReceived,
entry.exchangeFeeRate
);
}
}
function _convert(
bytes32 sourceCurrencyKey,
address from,
uint sourceAmountAfterSettlement,
bytes32 destinationCurrencyKey,
uint amountReceived,
address recipient,
bool virtualSynth
) internal returns (IVirtualSynth vSynth) {
// Burn the source amount
issuer().synths(sourceCurrencyKey).burn(from, sourceAmountAfterSettlement);
// Issue their new synths
ISynth dest = issuer().synths(destinationCurrencyKey);
if (virtualSynth) {
Proxyable synth = Proxyable(address(dest));
vSynth = _createVirtualSynth(IERC20(address(synth.proxy())), recipient, amountReceived, destinationCurrencyKey);
dest.issue(address(vSynth), amountReceived);
} else {
dest.issue(recipient, amountReceived);
}
}
function _createVirtualSynth(
IERC20,
address,
uint,
bytes32
) internal returns (IVirtualSynth) {
_notImplemented();
}
// Note: this function can intentionally be called by anyone on behalf of anyone else (the caller just pays the gas)
function settle(address from, bytes32 currencyKey)
external
returns (
uint reclaimed,
uint refunded,
uint numEntriesSettled
)
{
systemStatus().requireSynthActive(currencyKey);
return ExchangeSettlementLib.internalSettle(resolvedAddresses(), from, currencyKey, true, getWaitingPeriodSecs());
}
/* ========== INTERNAL FUNCTIONS ========== */
// gets the exchange parameters for a given direct integration (returns default params if no overrides exist)
function _exchangeSettings(address from, bytes32 currencyKey)
internal
view
returns (IDirectIntegrationManager.ParameterIntegrationSettings memory settings)
{
settings = directIntegrationManager().getExchangeParameters(from, currencyKey);
}
// runs basic checks and calls `rateWithSafetyChecks` (which can trigger circuit breakers)
// returns if there are any problems found with the rate of the given currencyKey but not reverted
function _ensureCanExchange(
bytes32 sourceCurrencyKey,
bytes32 destinationCurrencyKey,
uint sourceAmount
) internal returns (bool) {
require(sourceCurrencyKey != destinationCurrencyKey, "Can't be same synth");
require(sourceAmount > 0, "Zero amount");
(, bool srcBroken, bool srcStaleOrInvalid) =
sourceCurrencyKey != sUSD ? exchangeRates().rateWithSafetyChecks(sourceCurrencyKey) : (0, false, false);
(, bool dstBroken, bool dstStaleOrInvalid) =
destinationCurrencyKey != sUSD
? exchangeRates().rateWithSafetyChecks(destinationCurrencyKey)
: (0, false, false);
require(!srcStaleOrInvalid, "src rate stale or flagged");
require(!dstStaleOrInvalid, "dest rate stale or flagged");
return !srcBroken && !dstBroken;
}
// runs additional checks to verify a rate is valid at a specific round`
function _ensureCanExchangeAtRound(
bytes32 sourceCurrencyKey,
bytes32 destinationCurrencyKey,
uint roundIdForSrc,
uint roundIdForDest
) internal view {
require(sourceCurrencyKey != destinationCurrencyKey, "Can't be same synth");
bytes32[] memory synthKeys = new bytes32[](2);
synthKeys[0] = sourceCurrencyKey;
synthKeys[1] = destinationCurrencyKey;
uint[] memory roundIds = new uint[](2);
roundIds[0] = roundIdForSrc;
roundIds[1] = roundIdForDest;
require(!exchangeRates().anyRateIsInvalidAtRound(synthKeys, roundIds), "src/dest rate stale or flagged");
}
/* ========== Exchange Related Fees ========== */
/// @notice public function to get the total fee rate for a given exchange
/// @param sourceCurrencyKey The source currency key
/// @param destinationCurrencyKey The destination currency key
/// @return The exchange fee rate, and whether the rates are too volatile
function feeRateForExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey) external view returns (uint) {
IDirectIntegrationManager.ParameterIntegrationSettings memory sourceSettings =
_exchangeSettings(msg.sender, sourceCurrencyKey);
IDirectIntegrationManager.ParameterIntegrationSettings memory destinationSettings =
_exchangeSettings(msg.sender, destinationCurrencyKey);
(uint feeRate, bool tooVolatile) = _feeRateForExchange(sourceSettings, destinationSettings);
require(!tooVolatile, "too volatile");
return feeRate;
}
/// @notice public function to get the dynamic fee rate for a given exchange
/// @param sourceCurrencyKey The source currency key
/// @param destinationCurrencyKey The destination currency key
/// @return The exchange dynamic fee rate and if rates are too volatile
function dynamicFeeRateForExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey)
external
view
returns (uint feeRate, bool tooVolatile)
{
IDirectIntegrationManager.ParameterIntegrationSettings memory sourceSettings =
_exchangeSettings(msg.sender, sourceCurrencyKey);
IDirectIntegrationManager.ParameterIntegrationSettings memory destinationSettings =
_exchangeSettings(msg.sender, destinationCurrencyKey);
return _dynamicFeeRateForExchange(sourceSettings, destinationSettings);
}
/// @notice Calculate the exchange fee for a given source and destination currency key
/// @param sourceSettings The source currency key
/// @param destinationSettings The destination currency key
/// @return The exchange fee rate
/// @return The exchange dynamic fee rate and if rates are too volatile
function _feeRateForExchange(
IDirectIntegrationManager.ParameterIntegrationSettings memory sourceSettings,
IDirectIntegrationManager.ParameterIntegrationSettings memory destinationSettings
) internal view returns (uint feeRate, bool tooVolatile) {
// Get the exchange fee rate as per the source currencyKey and destination currencyKey
uint baseRate = sourceSettings.exchangeFeeRate.add(destinationSettings.exchangeFeeRate);
uint dynamicFee;
(dynamicFee, tooVolatile) = _dynamicFeeRateForExchange(sourceSettings, destinationSettings);
return (baseRate.add(dynamicFee), tooVolatile);
}
/// @notice Calculate the exchange fee for a given source and destination currency key
/// @param sourceSettings The source currency key
/// @param destinationSettings The destination currency key
/// @param roundIdForSrc The round id of the source currency.
/// @param roundIdForDest The round id of the target currency.
/// @return The exchange fee rate
/// @return The exchange dynamic fee rate
function _feeRateForExchangeAtRounds(
IDirectIntegrationManager.ParameterIntegrationSettings memory sourceSettings,
IDirectIntegrationManager.ParameterIntegrationSettings memory destinationSettings,
uint roundIdForSrc,
uint roundIdForDest
) internal view returns (uint feeRate, bool tooVolatile) {
// Get the exchange fee rate as per the source currencyKey and destination currencyKey
uint baseRate = sourceSettings.exchangeFeeRate.add(destinationSettings.exchangeFeeRate);
uint dynamicFee;
(dynamicFee, tooVolatile) = _dynamicFeeRateForExchangeAtRounds(
sourceSettings,
destinationSettings,
roundIdForSrc,
roundIdForDest
);
return (baseRate.add(dynamicFee), tooVolatile);
}
function _dynamicFeeRateForExchange(
IDirectIntegrationManager.ParameterIntegrationSettings memory sourceSettings,
IDirectIntegrationManager.ParameterIntegrationSettings memory destinationSettings
) internal view returns (uint dynamicFee, bool tooVolatile) {
(uint dynamicFeeDst, bool dstVolatile) = _dynamicFeeRateForCurrency(destinationSettings);
(uint dynamicFeeSrc, bool srcVolatile) = _dynamicFeeRateForCurrency(sourceSettings);
dynamicFee = dynamicFeeDst.add(dynamicFeeSrc);
// cap to maxFee
bool overMax = dynamicFee > sourceSettings.exchangeMaxDynamicFee;
dynamicFee = overMax ? sourceSettings.exchangeMaxDynamicFee : dynamicFee;
return (dynamicFee, overMax || dstVolatile || srcVolatile);
}
function _dynamicFeeRateForExchangeAtRounds(
IDirectIntegrationManager.ParameterIntegrationSettings memory sourceSettings,
IDirectIntegrationManager.ParameterIntegrationSettings memory destinationSettings,
uint roundIdForSrc,
uint roundIdForDest
) internal view returns (uint dynamicFee, bool tooVolatile) {
(uint dynamicFeeDst, bool dstVolatile) = _dynamicFeeRateForCurrencyRound(destinationSettings, roundIdForDest);
(uint dynamicFeeSrc, bool srcVolatile) = _dynamicFeeRateForCurrencyRound(sourceSettings, roundIdForSrc);
dynamicFee = dynamicFeeDst.add(dynamicFeeSrc);
// cap to maxFee
bool overMax = dynamicFee > sourceSettings.exchangeMaxDynamicFee;
dynamicFee = overMax ? sourceSettings.exchangeMaxDynamicFee : dynamicFee;
return (dynamicFee, overMax || dstVolatile || srcVolatile);
}
/// @notice Get dynamic dynamicFee for a given currency key (SIP-184)
/// @param settings The given currency key
/// @return The dynamic fee and if it exceeds max dynamic fee set in config
function _dynamicFeeRateForCurrency(IDirectIntegrationManager.ParameterIntegrationSettings memory settings)
internal
view
returns (uint dynamicFee, bool tooVolatile)
{
// no dynamic dynamicFee for sUSD or too few rounds
if (settings.currencyKey == sUSD || settings.exchangeDynamicFeeRounds <= 1) {
return (0, false);
}
uint roundId = exchangeRates().getCurrentRoundId(settings.currencyKey);
return _dynamicFeeRateForCurrencyRound(settings, roundId);
}
/// @notice Get dynamicFee for a given currency key (SIP-184)
/// @param settings The given currency key
/// @param roundId The round id
/// @return The dynamic fee and if it exceeds max dynamic fee set in config
function _dynamicFeeRateForCurrencyRound(
IDirectIntegrationManager.ParameterIntegrationSettings memory settings,
uint roundId
) internal view returns (uint dynamicFee, bool tooVolatile) {
// no dynamic dynamicFee for sUSD or too few rounds
if (settings.currencyKey == sUSD || settings.exchangeDynamicFeeRounds <= 1) {
return (0, false);
}
uint[] memory prices;
(prices, ) = exchangeRates().ratesAndUpdatedTimeForCurrencyLastNRounds(
settings.currencyKey,
settings.exchangeDynamicFeeRounds,
roundId
);
dynamicFee = _dynamicFeeCalculation(
prices,
settings.exchangeDynamicFeeThreshold,
settings.exchangeDynamicFeeWeightDecay
);
// cap to maxFee
bool overMax = dynamicFee > settings.exchangeMaxDynamicFee;
dynamicFee = overMax ? settings.exchangeMaxDynamicFee : dynamicFee;
return (dynamicFee, overMax);
}
/// @notice Calculate dynamic fee according to SIP-184
/// @param prices A list of prices from the current round to the previous rounds
/// @param threshold A threshold to clip the price deviation ratop
/// @param weightDecay A weight decay constant
/// @return uint dynamic fee rate as decimal
function _dynamicFeeCalculation(
uint[] memory prices,
uint threshold,
uint weightDecay
) internal pure returns (uint) {
// don't underflow
if (prices.length == 0) {
return 0;
}
uint dynamicFee = 0; // start with 0
// go backwards in price array
for (uint i = prices.length - 1; i > 0; i--) {
// apply decay from previous round (will be 0 for first round)
dynamicFee = dynamicFee.multiplyDecimal(weightDecay);
// calculate price deviation
uint deviation = _thresholdedAbsDeviationRatio(prices[i - 1], prices[i], threshold);
// add to total fee
dynamicFee = dynamicFee.add(deviation);
}
return dynamicFee;
}
/// absolute price deviation ratio used by dynamic fee calculation
/// deviationRatio = (abs(current - previous) / previous) - threshold
/// if negative, zero is returned
function _thresholdedAbsDeviationRatio(
uint price,
uint previousPrice,
uint threshold
) internal pure returns (uint) {
if (previousPrice == 0) {
return 0; // don't divide by zero
}
// abs difference between prices
uint absDelta = price > previousPrice ? price - previousPrice : previousPrice - price;
// relative to previous price
uint deviationRatio = absDelta.divideDecimal(previousPrice);
// only the positive difference from threshold
return deviationRatio > threshold ? deviationRatio - threshold : 0;
}
function getAmountsForExchange(
uint sourceAmount,
bytes32 sourceCurrencyKey,
bytes32 destinationCurrencyKey
)
external
view
returns (
uint amountReceived,
uint fee,
uint exchangeFeeRate
)
{
IDirectIntegrationManager.ParameterIntegrationSettings memory sourceSettings =
_exchangeSettings(msg.sender, sourceCurrencyKey);
IDirectIntegrationManager.ParameterIntegrationSettings memory destinationSettings =
_exchangeSettings(msg.sender, destinationCurrencyKey);
require(sourceCurrencyKey == sUSD || !exchangeRates().rateIsInvalid(sourceCurrencyKey), "src synth rate invalid");
require(
destinationCurrencyKey == sUSD || !exchangeRates().rateIsInvalid(destinationCurrencyKey),
"dest synth rate invalid"
);
// The checks are added for consistency with the checks performed in _exchange()
// The reverts (instead of no-op returns) are used order to prevent incorrect usage in calling contracts
// (The no-op in _exchange() is in order to trigger system suspension if needed)
// check synths active
systemStatus().requireSynthActive(sourceCurrencyKey);
systemStatus().requireSynthActive(destinationCurrencyKey);
bool tooVolatile;
(exchangeFeeRate, tooVolatile) = _feeRateForExchange(sourceSettings, destinationSettings);
// check rates volatility result
require(!tooVolatile, "exchange rates too volatile");
(uint destinationAmount, , ) =
exchangeRates().effectiveValueAndRates(sourceCurrencyKey, sourceAmount, destinationCurrencyKey);
amountReceived = ExchangeSettlementLib._deductFeesFromAmount(destinationAmount, exchangeFeeRate);
fee = destinationAmount.sub(amountReceived);
}
function _notImplemented() internal pure {
revert("Cannot be run on this layer");
}
// ========== MODIFIERS ==========
modifier onlySynthetixorSynth() {
ISynthetix _synthetix = synthetix();
require(
msg.sender == address(_synthetix) || _synthetix.synthsByAddress(msg.sender) != bytes32(0),
"Exchanger: Only synthetix or a synth contract can perform this action"
);
_;
}
// ========== EVENTS ==========
// note bot hof these events are actually emitted from `ExchangeSettlementLib`
// but they are defined here for interface reasons
event ExchangeEntryAppended(
address indexed account,
bytes32 src,
uint256 amount,
bytes32 dest,
uint256 amountReceived,
uint256 exchangeFeeRate,
uint256 roundIdForSrc,
uint256 roundIdForDest
);
event ExchangeEntrySettled(
address indexed from,
bytes32 src,
uint256 amount,
bytes32 dest,
uint256 reclaim,
uint256 rebate,
uint256 srcRoundIdAtPeriodEnd,
uint256 destRoundIdAtPeriodEnd,
uint256 exchangeTimestamp
);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
pragma experimental ABIEncoderV2;
// Inheritance
import "./Owned.sol";
import "./MixinSystemSettings.sol";
import "./MixinResolver.sol";
import "./IERC20.sol";
import "./IExchangeRates.sol";
// Libraries
import "./SafeDecimalMath.sol";
// Internal references
// AggregatorInterface from Chainlink represents a decentralized pricing network for a single currency key
import "./AggregatorV2V3Interface.sol";
// FlagsInterface from Chainlink addresses SIP-76
import "./FlagsInterface.sol";
import "./ICircuitBreaker.sol";
// https://docs.synthetix.io/contracts/source/contracts/exchangerates
contract ExchangeRates is Owned, MixinSystemSettings, IExchangeRates {
using SafeMath for uint;
using SafeDecimalMath for uint;
bytes32 public constant CONTRACT_NAME = "ExchangeRates";
bytes32 internal constant CONTRACT_CIRCUIT_BREAKER = "CircuitBreaker";
//slither-disable-next-line naming-convention
bytes32 internal constant sUSD = "sUSD";
// Decentralized oracle networks that feed into pricing aggregators
mapping(bytes32 => AggregatorV2V3Interface) public aggregators;
mapping(bytes32 => uint8) public currencyKeyDecimals;
// List of aggregator keys for convenient iteration
bytes32[] public aggregatorKeys;
// ========== CONSTRUCTOR ==========
constructor(address _owner, address _resolver) public Owned(_owner) MixinSystemSettings(_resolver) {}
/* ========== MUTATIVE FUNCTIONS ========== */
function addAggregator(bytes32 currencyKey, address aggregatorAddress) external onlyOwner {
AggregatorV2V3Interface aggregator = AggregatorV2V3Interface(aggregatorAddress);
// This check tries to make sure that a valid aggregator is being added.
// It checks if the aggregator is an existing smart contract that has implemented `latestTimestamp` function.
require(aggregator.latestRound() >= 0, "Given Aggregator is invalid");
uint8 decimals = aggregator.decimals();
// This contract converts all external rates to 18 decimal rates, so adding external rates with
// higher precision will result in losing precision internally. 27 decimals will result in losing 9 decimal
// places, which should leave plenty precision for most things.
require(decimals <= 27, "Aggregator decimals should be lower or equal to 27");
if (address(aggregators[currencyKey]) == address(0)) {
aggregatorKeys.push(currencyKey);
}
aggregators[currencyKey] = aggregator;
currencyKeyDecimals[currencyKey] = decimals;
emit AggregatorAdded(currencyKey, address(aggregator));
}
function removeAggregator(bytes32 currencyKey) external onlyOwner {
address aggregator = address(aggregators[currencyKey]);
require(aggregator != address(0), "No aggregator exists for key");
delete aggregators[currencyKey];
delete currencyKeyDecimals[currencyKey];
bool wasRemoved = removeFromArray(currencyKey, aggregatorKeys);
if (wasRemoved) {
emit AggregatorRemoved(currencyKey, aggregator);
}
}
function rateWithSafetyChecks(bytes32 currencyKey)
external
returns (
uint rate,
bool broken,
bool staleOrInvalid
)
{
address aggregatorAddress = address(aggregators[currencyKey]);
require(currencyKey == sUSD || aggregatorAddress != address(0), "No aggregator for asset");
RateAndUpdatedTime memory rateAndTime = _getRateAndUpdatedTime(currencyKey);
if (currencyKey == sUSD) {
return (rateAndTime.rate, false, false);
}
rate = rateAndTime.rate;
broken = circuitBreaker().probeCircuitBreaker(aggregatorAddress, rateAndTime.rate);
staleOrInvalid =
_rateIsStaleWithTime(getRateStalePeriod(), rateAndTime.time) ||
_rateIsFlagged(currencyKey, FlagsInterface(getAggregatorWarningFlags()));
}
/* ========== VIEWS ========== */
function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
bytes32[] memory existingAddresses = MixinSystemSettings.resolverAddressesRequired();
bytes32[] memory newAddresses = new bytes32[](1);
newAddresses[0] = CONTRACT_CIRCUIT_BREAKER;
return combineArrays(existingAddresses, newAddresses);
}
function circuitBreaker() internal view returns (ICircuitBreaker) {
return ICircuitBreaker(requireAndGetAddress(CONTRACT_CIRCUIT_BREAKER));
}
function currenciesUsingAggregator(address aggregator) external view returns (bytes32[] memory currencies) {
uint count = 0;
currencies = new bytes32[](aggregatorKeys.length);
for (uint i = 0; i < aggregatorKeys.length; i++) {
bytes32 currencyKey = aggregatorKeys[i];
if (address(aggregators[currencyKey]) == aggregator) {
currencies[count++] = currencyKey;
}
}
}
function rateStalePeriod() external view returns (uint) {
return getRateStalePeriod();
}
function aggregatorWarningFlags() external view returns (address) {
return getAggregatorWarningFlags();
}
function rateAndUpdatedTime(bytes32 currencyKey) external view returns (uint rate, uint time) {
RateAndUpdatedTime memory rateAndTime = _getRateAndUpdatedTime(currencyKey);
return (rateAndTime.rate, rateAndTime.time);
}
function getLastRoundIdBeforeElapsedSecs(
bytes32 currencyKey,
uint startingRoundId,
uint startingTimestamp,
uint timediff
) external view returns (uint) {
uint roundId = startingRoundId;
uint nextTimestamp = 0;
while (true) {
(, nextTimestamp) = _getRateAndTimestampAtRound(currencyKey, roundId + 1);
// if there's no new round, then the previous roundId was the latest
if (nextTimestamp == 0 || nextTimestamp > startingTimestamp + timediff) {
return roundId;
}
roundId++;
}
return roundId;
}
function getCurrentRoundId(bytes32 currencyKey) external view returns (uint) {
return _getCurrentRoundId(currencyKey);
}
function effectiveValueAndRatesAtRound(
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey,
uint roundIdForSrc,
uint roundIdForDest
)
external
view
returns (
uint value,
uint sourceRate,
uint destinationRate
)
{
(sourceRate, ) = _getRateAndTimestampAtRound(sourceCurrencyKey, roundIdForSrc);
// If there's no change in the currency, then just return the amount they gave us
if (sourceCurrencyKey == destinationCurrencyKey) {
destinationRate = sourceRate;
value = sourceAmount;
} else {
(destinationRate, ) = _getRateAndTimestampAtRound(destinationCurrencyKey, roundIdForDest);
// prevent divide-by 0 error (this happens if the dest is not a valid rate)
if (destinationRate > 0) {
// Calculate the effective value by going from source -> USD -> destination
value = sourceAmount.multiplyDecimalRound(sourceRate).divideDecimalRound(destinationRate);
}
}
}
function rateAndTimestampAtRound(bytes32 currencyKey, uint roundId) external view returns (uint rate, uint time) {
return _getRateAndTimestampAtRound(currencyKey, roundId);
}
function lastRateUpdateTimes(bytes32 currencyKey) external view returns (uint256) {
return _getUpdatedTime(currencyKey);
}
function lastRateUpdateTimesForCurrencies(bytes32[] calldata currencyKeys) external view returns (uint[] memory) {
uint[] memory lastUpdateTimes = new uint[](currencyKeys.length);
for (uint i = 0; i < currencyKeys.length; i++) {
lastUpdateTimes[i] = _getUpdatedTime(currencyKeys[i]);
}
return lastUpdateTimes;
}
function effectiveValue(
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey
) external view returns (uint value) {
(value, , ) = _effectiveValueAndRates(sourceCurrencyKey, sourceAmount, destinationCurrencyKey);
}
function effectiveValueAndRates(
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey
)
external
view
returns (
uint value,
uint sourceRate,
uint destinationRate
)
{
return _effectiveValueAndRates(sourceCurrencyKey, sourceAmount, destinationCurrencyKey);
}
// SIP-120 Atomic exchanges
function effectiveAtomicValueAndRates(
bytes32,
uint,
bytes32
)
public
view
returns (
uint value,
uint systemValue,
uint systemSourceRate,
uint systemDestinationRate
)
{
_notImplemented();
}
function effectiveAtomicValueAndRates(
IDirectIntegrationManager.ParameterIntegrationSettings memory,
uint,
IDirectIntegrationManager.ParameterIntegrationSettings memory,
IDirectIntegrationManager.ParameterIntegrationSettings memory
)
public
view
returns (
uint value,
uint systemValue,
uint systemSourceRate,
uint systemDestinationRate
)
{
_notImplemented();
}
function rateForCurrency(bytes32 currencyKey) external view returns (uint) {
return _getRateAndUpdatedTime(currencyKey).rate;
}
/// @notice getting N rounds of rates for a currency at a specific round
/// @param currencyKey the currency key
/// @param numRounds the number of rounds to get
/// @param roundId the round id
/// @return a list of rates and a list of times
function ratesAndUpdatedTimeForCurrencyLastNRounds(
bytes32 currencyKey,
uint numRounds,
uint roundId
) external view returns (uint[] memory rates, uint[] memory times) {
rates = new uint[](numRounds);
times = new uint[](numRounds);
roundId = roundId > 0 ? roundId : _getCurrentRoundId(currencyKey);
for (uint i = 0; i < numRounds; i++) {
// fetch the rate and treat is as current, so inverse limits if frozen will always be applied
// regardless of current rate
(rates[i], times[i]) = _getRateAndTimestampAtRound(currencyKey, roundId);
if (roundId == 0) {
// if we hit the last round, then return what we have
return (rates, times);
} else {
roundId--;
}
}
}
function ratesForCurrencies(bytes32[] calldata currencyKeys) external view returns (uint[] memory) {
uint[] memory _localRates = new uint[](currencyKeys.length);
for (uint i = 0; i < currencyKeys.length; i++) {
_localRates[i] = _getRate(currencyKeys[i]);
}
return _localRates;
}
function rateAndInvalid(bytes32 currencyKey) public view returns (uint rate, bool isInvalid) {
RateAndUpdatedTime memory rateAndTime = _getRateAndUpdatedTime(currencyKey);
if (currencyKey == sUSD) {
return (rateAndTime.rate, false);
}
return (
rateAndTime.rate,
_rateIsStaleWithTime(getRateStalePeriod(), rateAndTime.time) ||
_rateIsFlagged(currencyKey, FlagsInterface(getAggregatorWarningFlags())) ||
_rateIsCircuitBroken(currencyKey, rateAndTime.rate)
);
}
function ratesAndInvalidForCurrencies(bytes32[] calldata currencyKeys)
external
view
returns (uint[] memory rates, bool anyRateInvalid)
{
rates = new uint[](currencyKeys.length);
uint256 _rateStalePeriod = getRateStalePeriod();
// fetch all flags at once
bool[] memory flagList = getFlagsForRates(currencyKeys);
for (uint i = 0; i < currencyKeys.length; i++) {
// do one lookup of the rate & time to minimize gas
RateAndUpdatedTime memory rateEntry = _getRateAndUpdatedTime(currencyKeys[i]);
rates[i] = rateEntry.rate;
if (!anyRateInvalid && currencyKeys[i] != sUSD) {
anyRateInvalid =
flagList[i] ||
_rateIsStaleWithTime(_rateStalePeriod, rateEntry.time) ||
_rateIsCircuitBroken(currencyKeys[i], rateEntry.rate);
}
}
}
function rateIsStale(bytes32 currencyKey) external view returns (bool) {
return _rateIsStale(currencyKey, getRateStalePeriod());
}
function rateIsInvalid(bytes32 currencyKey) external view returns (bool) {
(, bool invalid) = rateAndInvalid(currencyKey);
return invalid;
}
function rateIsFlagged(bytes32 currencyKey) external view returns (bool) {
return _rateIsFlagged(currencyKey, FlagsInterface(getAggregatorWarningFlags()));
}
function anyRateIsInvalid(bytes32[] calldata currencyKeys) external view returns (bool) {
// Loop through each key and check whether the data point is stale.
uint256 _rateStalePeriod = getRateStalePeriod();
bool[] memory flagList = getFlagsForRates(currencyKeys);
for (uint i = 0; i < currencyKeys.length; i++) {
if (currencyKeys[i] == sUSD) {
continue;
}
RateAndUpdatedTime memory rateEntry = _getRateAndUpdatedTime(currencyKeys[i]);
if (
flagList[i] ||
_rateIsStaleWithTime(_rateStalePeriod, rateEntry.time) ||
_rateIsCircuitBroken(currencyKeys[i], rateEntry.rate)
) {
return true;
}
}
return false;
}
/// this method checks whether any rate is:
/// 1. flagged
/// 2. stale with respect to current time (now)
function anyRateIsInvalidAtRound(bytes32[] calldata currencyKeys, uint[] calldata roundIds)
external
view
returns (bool)
{
// Loop through each key and check whether the data point is stale.
require(roundIds.length == currencyKeys.length, "roundIds must be the same length as currencyKeys");
uint256 _rateStalePeriod = getRateStalePeriod();
bool[] memory flagList = getFlagsForRates(currencyKeys);
for (uint i = 0; i < currencyKeys.length; i++) {
if (currencyKeys[i] == sUSD) {
continue;
}
// NOTE: technically below `_rateIsStaleWithTime` is supposed to be called with the roundId timestamp in consideration, and `_rateIsCircuitBroken` is supposed to be
// called with the current rate (or just not called at all)
// but thats not how the functionality has worked prior to this change so that is why it works this way here
// if you are adding new code taht calls this function and the rate is a long time ago, note that this function may resolve an invalid rate when its actually valid!
(uint rate, uint time) = _getRateAndTimestampAtRound(currencyKeys[i], roundIds[i]);
if (flagList[i] || _rateIsStaleWithTime(_rateStalePeriod, time) || _rateIsCircuitBroken(currencyKeys[i], rate)) {
return true;
}
}
return false;
}
function synthTooVolatileForAtomicExchange(bytes32) public view returns (bool) {
_notImplemented();
}
function synthTooVolatileForAtomicExchange(IDirectIntegrationManager.ParameterIntegrationSettings memory)
public
view
returns (bool)
{
_notImplemented();
}
/* ========== INTERNAL FUNCTIONS ========== */
function getFlagsForRates(bytes32[] memory currencyKeys) internal view returns (bool[] memory flagList) {
FlagsInterface _flags = FlagsInterface(getAggregatorWarningFlags());
// fetch all flags at once
if (_flags != FlagsInterface(0)) {
address[] memory _aggregators = new address[](currencyKeys.length);
for (uint i = 0; i < currencyKeys.length; i++) {
_aggregators[i] = address(aggregators[currencyKeys[i]]);
}
flagList = _flags.getFlags(_aggregators);
} else {
flagList = new bool[](currencyKeys.length);
}
}
function removeFromArray(bytes32 entry, bytes32[] storage array) internal returns (bool) {
for (uint i = 0; i < array.length; i++) {
if (array[i] == entry) {
delete array[i];
// Copy the last key into the place of the one we just deleted
// If there's only one key, this is array[0] = array[0].
// If we're deleting the last one, it's also a NOOP in the same way.
array[i] = array[array.length - 1];
// Decrease the size of the array by one.
array.length--;
return true;
}
}
return false;
}
function _formatAggregatorAnswer(bytes32 currencyKey, int256 rate) internal view returns (uint) {
require(rate >= 0, "Negative rate not supported");
uint decimals = currencyKeyDecimals[currencyKey];
uint result = uint(rate);
if (decimals == 0 || decimals == 18) {
// do not convert for 0 (part of implicit interface), and not needed for 18
} else if (decimals < 18) {
// increase precision to 18
uint multiplier = 10**(18 - decimals); // SafeMath not needed since decimals is small
result = result.mul(multiplier);
} else if (decimals > 18) {
// decrease precision to 18
uint divisor = 10**(decimals - 18); // SafeMath not needed since decimals is small
result = result.div(divisor);
}
return result;
}
function _getRateAndUpdatedTime(bytes32 currencyKey) internal view returns (RateAndUpdatedTime memory) {
// sUSD rate is 1.0
if (currencyKey == sUSD) {
return RateAndUpdatedTime({rate: uint216(SafeDecimalMath.unit()), time: 0});
} else {
AggregatorV2V3Interface aggregator = aggregators[currencyKey];
if (aggregator != AggregatorV2V3Interface(0)) {
// this view from the aggregator is the most gas efficient but it can throw when there's no data,
// so let's call it low-level to suppress any reverts
bytes memory payload = abi.encodeWithSignature("latestRoundData()");
// solhint-disable avoid-low-level-calls
// slither-disable-next-line low-level-calls
(bool success, bytes memory returnData) = address(aggregator).staticcall(payload);
if (success) {
(, int256 answer, , uint256 updatedAt, ) =
abi.decode(returnData, (uint80, int256, uint256, uint256, uint80));
return
RateAndUpdatedTime({
rate: uint216(_formatAggregatorAnswer(currencyKey, answer)),
time: uint40(updatedAt)
});
} // else return defaults, to avoid reverting in views
} // else return defaults, to avoid reverting in views
}
}
function _getCurrentRoundId(bytes32 currencyKey) internal view returns (uint) {
if (currencyKey == sUSD) {
return 0;
}
AggregatorV2V3Interface aggregator = aggregators[currencyKey];
if (aggregator != AggregatorV2V3Interface(0)) {
return aggregator.latestRound();
} // else return defaults, to avoid reverting in views
}
function _getRateAndTimestampAtRound(bytes32 currencyKey, uint roundId) internal view returns (uint rate, uint time) {
// short circuit sUSD
if (currencyKey == sUSD) {
// sUSD has no rounds, and 0 time is preferrable for "volatility" heuristics
// which are used in atomic swaps and fee reclamation
return (SafeDecimalMath.unit(), 0);
} else {
AggregatorV2V3Interface aggregator = aggregators[currencyKey];
if (aggregator != AggregatorV2V3Interface(0)) {
// this view from the aggregator is the most gas efficient but it can throw when there's no data,
// so let's call it low-level to suppress any reverts
bytes memory payload = abi.encodeWithSignature("getRoundData(uint80)", roundId);
// solhint-disable avoid-low-level-calls
(bool success, bytes memory returnData) = address(aggregator).staticcall(payload);
if (success) {
(, int256 answer, , uint256 updatedAt, ) =
abi.decode(returnData, (uint80, int256, uint256, uint256, uint80));
return (_formatAggregatorAnswer(currencyKey, answer), updatedAt);
} // else return defaults, to avoid reverting in views
} // else return defaults, to avoid reverting in views
}
}
function _getRate(bytes32 currencyKey) internal view returns (uint256) {
return _getRateAndUpdatedTime(currencyKey).rate;
}
function _getUpdatedTime(bytes32 currencyKey) internal view returns (uint256) {
return _getRateAndUpdatedTime(currencyKey).time;
}
function _effectiveValueAndRates(
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey
)
internal
view
returns (
uint value,
uint sourceRate,
uint destinationRate
)
{
sourceRate = _getRate(sourceCurrencyKey);
// If there's no change in the currency, then just return the amount they gave us
if (sourceCurrencyKey == destinationCurrencyKey) {
destinationRate = sourceRate;
value = sourceAmount;
} else {
// Calculate the effective value by going from source -> USD -> destination
destinationRate = _getRate(destinationCurrencyKey);
// prevent divide-by 0 error (this happens if the dest is not a valid rate)
if (destinationRate > 0) {
value = sourceAmount.multiplyDecimalRound(sourceRate).divideDecimalRound(destinationRate);
}
}
}
function _rateIsStale(bytes32 currencyKey, uint _rateStalePeriod) internal view returns (bool) {
// sUSD is a special case and is never stale (check before an SLOAD of getRateAndUpdatedTime)
if (currencyKey == sUSD) {
return false;
}
return _rateIsStaleWithTime(_rateStalePeriod, _getUpdatedTime(currencyKey));
}
function _rateIsStaleWithTime(uint _rateStalePeriod, uint _time) internal view returns (bool) {
return _time.add(_rateStalePeriod) < now;
}
function _rateIsFlagged(bytes32 currencyKey, FlagsInterface flags) internal view returns (bool) {
// sUSD is a special case and is never invalid
if (currencyKey == sUSD) {
return false;
}
address aggregator = address(aggregators[currencyKey]);
// when no aggregator or when the flags haven't been setup
if (aggregator == address(0) || flags == FlagsInterface(0)) {
return false;
}
return flags.getFlag(aggregator);
}
function _rateIsCircuitBroken(bytes32 currencyKey, uint curRate) internal view returns (bool) {
return circuitBreaker().isInvalid(address(aggregators[currencyKey]), curRate);
}
function _notImplemented() internal pure {
// slither-disable-next-line dead-code
revert("Cannot be run on this layer");
}
/* ========== EVENTS ========== */
event AggregatorAdded(bytes32 currencyKey, address aggregator);
event AggregatorRemoved(bytes32 currencyKey, address aggregator);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
pragma experimental ABIEncoderV2;
// Inheritance
import "./ExchangeRates.sol";
import "./IDexPriceAggregator.sol";
// https://docs.synthetix.io/contracts/source/contracts/exchangerateswithdexpricing
contract ExchangeRatesWithDexPricing is ExchangeRates {
bytes32 public constant CONTRACT_NAME = "ExchangeRatesWithDexPricing";
bytes32 internal constant SETTING_DEX_PRICE_AGGREGATOR = "dexPriceAggregator";
constructor(address _owner, address _resolver) public ExchangeRates(_owner, _resolver) {}
/* ========== ADDRESS RESOLVER CONFIGURATION ========== */
bytes32 private constant CONTRACT_DIRECT_INTEGRATION_MANAGER = "DirectIntegrationManager";
function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
bytes32[] memory existingAddresses = ExchangeRates.resolverAddressesRequired();
bytes32[] memory newAddresses = new bytes32[](1);
newAddresses[0] = CONTRACT_DIRECT_INTEGRATION_MANAGER;
addresses = combineArrays(existingAddresses, newAddresses);
}
/* ========== SETTERS ========== */
function setDexPriceAggregator(IDexPriceAggregator _dexPriceAggregator) external onlyOwner {
flexibleStorage().setAddressValue(
ExchangeRates.CONTRACT_NAME,
SETTING_DEX_PRICE_AGGREGATOR,
address(_dexPriceAggregator)
);
emit DexPriceAggregatorUpdated(address(_dexPriceAggregator));
}
/* ========== VIEWS ========== */
function directIntegrationManager() internal view returns (IDirectIntegrationManager) {
return IDirectIntegrationManager(requireAndGetAddress(CONTRACT_DIRECT_INTEGRATION_MANAGER));
}
function dexPriceAggregator() public view returns (IDexPriceAggregator) {
return
IDexPriceAggregator(
flexibleStorage().getAddressValue(ExchangeRates.CONTRACT_NAME, SETTING_DEX_PRICE_AGGREGATOR)
);
}
// SIP-120 Atomic exchanges
function effectiveAtomicValueAndRates(
bytes32 sourceCurrencyKey,
uint amount,
bytes32 destinationCurrencyKey
)
public
view
returns (
uint value,
uint systemValue,
uint systemSourceRate,
uint systemDestinationRate
)
{
IDirectIntegrationManager.ParameterIntegrationSettings memory sourceSettings =
directIntegrationManager().getExchangeParameters(msg.sender, sourceCurrencyKey);
IDirectIntegrationManager.ParameterIntegrationSettings memory destinationSettings =
directIntegrationManager().getExchangeParameters(msg.sender, destinationCurrencyKey);
IDirectIntegrationManager.ParameterIntegrationSettings memory usdSettings =
directIntegrationManager().getExchangeParameters(msg.sender, sUSD);
return effectiveAtomicValueAndRates(sourceSettings, amount, destinationSettings, usdSettings);
}
// SIP-120 Atomic exchanges
// Note that the returned systemValue, systemSourceRate, and systemDestinationRate are based on
// the current system rate, which may not be the atomic rate derived from value / sourceAmount
function effectiveAtomicValueAndRates(
IDirectIntegrationManager.ParameterIntegrationSettings memory sourceSettings,
uint sourceAmount,
IDirectIntegrationManager.ParameterIntegrationSettings memory destinationSettings,
IDirectIntegrationManager.ParameterIntegrationSettings memory usdSettings
)
public
view
returns (
uint value,
uint systemValue,
uint systemSourceRate,
uint systemDestinationRate
)
{
(systemValue, systemSourceRate, systemDestinationRate) = _effectiveValueAndRates(
sourceSettings.currencyKey,
sourceAmount,
destinationSettings.currencyKey
);
bool usePureChainlinkPriceForSource = getPureChainlinkPriceForAtomicSwapsEnabled(sourceSettings.currencyKey);
bool usePureChainlinkPriceForDest = getPureChainlinkPriceForAtomicSwapsEnabled(destinationSettings.currencyKey);
uint sourceRate;
uint destRate;
// Handle the different scenarios that may arise when trading currencies with or without the PureChainlinkPrice set.
// outlined here: https://sips.synthetix.io/sips/sip-198/#computation-methodology-in-atomic-pricing
if (usePureChainlinkPriceForSource) {
sourceRate = systemSourceRate;
} else {
sourceRate = _getMinValue(
systemSourceRate,
_getPriceFromDexAggregator(sourceSettings, usdSettings, sourceAmount)
);
}
if (usePureChainlinkPriceForDest) {
destRate = systemDestinationRate;
} else {
destRate = _getMaxValue(
systemDestinationRate,
_getPriceFromDexAggregator(usdSettings, destinationSettings, sourceAmount)
);
}
value = sourceAmount.mul(sourceRate).div(destRate);
}
function _getMinValue(uint x, uint y) internal pure returns (uint) {
return x < y ? x : y;
}
function _getMaxValue(uint x, uint y) internal pure returns (uint) {
return x > y ? x : y;
}
/// @notice Retrieve the TWAP (time-weighted average price) of an asset from its Uniswap V3-equivalent pool
/// @dev By default, the TWAP oracle 'hops' through the wETH pool. This can be overridden. See DexPriceAggregator for more information.
/// @dev The TWAP oracle doesn't take into account variable slippage due to trade amounts, as Uniswap's OracleLibary doesn't cross ticks based on their liquidity. See: https://docs.uniswap.org/protocol/concepts/V3-overview/oracle#deriving-price-from-a-tick
/// @dev One of `sourceCurrencyKey` or `destCurrencyKey` should be sUSD. There are two parameters to indicate directionality. Because this function returns "price", if the source is sUSD, the result will be flipped.
/// @param sourceSettings The settings data for the source token
/// @param destinationSettings The settings data for the destination token
/// @param amount The amount of the asset we're interested in
/// @return The price of the asset
function _getPriceFromDexAggregator(
IDirectIntegrationManager.ParameterIntegrationSettings memory sourceSettings,
IDirectIntegrationManager.ParameterIntegrationSettings memory destinationSettings,
uint amount
) internal view returns (uint) {
require(amount != 0, "Amount must be greater than 0");
require(
sourceSettings.currencyKey == sUSD || destinationSettings.currencyKey == sUSD,
"Atomic swaps must go through sUSD"
);
IERC20 sourceEquivalent = IERC20(sourceSettings.atomicEquivalentForDexPricing);
require(address(sourceEquivalent) != address(0), "No atomic equivalent for source");
IERC20 destEquivalent = IERC20(destinationSettings.atomicEquivalentForDexPricing);
require(address(destEquivalent) != address(0), "No atomic equivalent for dest");
uint result =
_dexPriceDestinationValue(
IDexPriceAggregator(sourceSettings.dexPriceAggregator),
sourceEquivalent,
destEquivalent,
amount,
sourceSettings
.atomicTwapWindow
)
.mul(SafeDecimalMath.unit())
.div(amount);
require(result != 0, "Result must be greater than 0");
return destinationSettings.currencyKey == "sUSD" ? result : SafeDecimalMath.unit().divideDecimalRound(result);
}
function _dexPriceDestinationValue(
IDexPriceAggregator dexAggregator,
IERC20 sourceEquivalent,
IERC20 destEquivalent,
uint sourceAmount,
uint twapWindow
) internal view returns (uint) {
// Normalize decimals in case equivalent asset uses different decimals from internal unit
uint sourceAmountInEquivalent =
(sourceAmount.mul(10**uint(sourceEquivalent.decimals()))).div(SafeDecimalMath.unit());
require(address(dexAggregator) != address(0), "dex aggregator address is 0");
require(twapWindow != 0, "Uninitialized atomic twap window");
uint twapValueInEquivalent =
dexAggregator.assetToAsset(
address(sourceEquivalent),
sourceAmountInEquivalent,
address(destEquivalent),
twapWindow
);
require(twapValueInEquivalent > 0, "dex price returned 0");
// Similar to source amount, normalize decimals back to internal unit for output amount
return (twapValueInEquivalent.mul(SafeDecimalMath.unit())).div(10**uint(destEquivalent.decimals()));
}
function synthTooVolatileForAtomicExchange(bytes32 currencyKey) public view returns (bool) {
IDirectIntegrationManager.ParameterIntegrationSettings memory settings =
directIntegrationManager().getExchangeParameters(msg.sender, currencyKey);
return synthTooVolatileForAtomicExchange(settings);
}
function synthTooVolatileForAtomicExchange(IDirectIntegrationManager.ParameterIntegrationSettings memory settings)
public
view
returns (bool)
{
// sUSD is a special case and is never volatile
if (settings.currencyKey == "sUSD") return false;
uint considerationWindow = settings.atomicVolatilityConsiderationWindow;
uint updateThreshold = settings.atomicVolatilityUpdateThreshold;
if (considerationWindow == 0 || updateThreshold == 0) {
// If either volatility setting is not set, never judge an asset to be volatile
return false;
}
// Go back through the historical oracle update rounds to see if there have been more
// updates in the consideration window than the allowed threshold.
// If there have, consider the asset volatile--by assumption that many close-by oracle
// updates is a good proxy for price volatility.
uint considerationWindowStart = block.timestamp.sub(considerationWindow);
uint roundId = _getCurrentRoundId(settings.currencyKey);
for (updateThreshold; updateThreshold > 0; updateThreshold--) {
(uint rate, uint time) = _getRateAndTimestampAtRound(settings.currencyKey, roundId);
if (time != 0 && time < considerationWindowStart) {
// Round was outside consideration window so we can stop querying further rounds
return false;
} else if (rate == 0 || time == 0) {
// Either entire round or a rate inside consideration window was not available
// Consider the asset volatile
break;
}
if (roundId == 0) {
// Not enough historical data to continue further
// Consider the asset volatile
break;
}
roundId--;
}
return true;
}
/* ========== EVENTS ========== */
event DexPriceAggregatorUpdated(address newDexPriceAggregator);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
pragma experimental ABIEncoderV2;
// Inheritance
import "./Exchanger.sol";
// Internal references
import "./MinimalProxyFactory.sol";
import "./IAddressResolver.sol";
import "./IDirectIntegrationManager.sol";
import "./IERC20.sol";
interface IVirtualSynthInternal {
function initialize(
IERC20 _synth,
IAddressResolver _resolver,
address _recipient,
uint _amount,
bytes32 _currencyKey
) external;
}
// https://docs.synthetix.io/contracts/source/contracts/exchangerwithfeereclamationalternatives
contract ExchangerWithFeeRecAlternatives is MinimalProxyFactory, Exchanger {
bytes32 public constant CONTRACT_NAME = "ExchangerWithFeeRecAlternatives";
using SafeMath for uint;
struct ExchangeVolumeAtPeriod {
uint64 time;
uint192 volume;
}
ExchangeVolumeAtPeriod public lastAtomicVolume;
constructor(address _owner, address _resolver) public MinimalProxyFactory() Exchanger(_owner, _resolver) {}
/* ========== ADDRESS RESOLVER CONFIGURATION ========== */
bytes32 private constant CONTRACT_VIRTUALSYNTH_MASTERCOPY = "VirtualSynthMastercopy";
function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
bytes32[] memory existingAddresses = Exchanger.resolverAddressesRequired();
bytes32[] memory newAddresses = new bytes32[](1);
newAddresses[0] = CONTRACT_VIRTUALSYNTH_MASTERCOPY;
addresses = combineArrays(existingAddresses, newAddresses);
}
/* ========== VIEWS ========== */
function atomicMaxVolumePerBlock() external view returns (uint) {
return getAtomicMaxVolumePerBlock();
}
function feeRateForAtomicExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey)
external
view
returns (uint exchangeFeeRate)
{
IDirectIntegrationManager.ParameterIntegrationSettings memory sourceSettings =
_exchangeSettings(msg.sender, sourceCurrencyKey);
IDirectIntegrationManager.ParameterIntegrationSettings memory destinationSettings =
_exchangeSettings(msg.sender, destinationCurrencyKey);
exchangeFeeRate = _feeRateForAtomicExchange(sourceSettings, destinationSettings);
}
function getAmountsForAtomicExchange(
uint sourceAmount,
bytes32 sourceCurrencyKey,
bytes32 destinationCurrencyKey
)
external
view
returns (
uint amountReceived,
uint fee,
uint exchangeFeeRate
)
{
IDirectIntegrationManager.ParameterIntegrationSettings memory sourceSettings =
_exchangeSettings(msg.sender, sourceCurrencyKey);
IDirectIntegrationManager.ParameterIntegrationSettings memory destinationSettings =
_exchangeSettings(msg.sender, destinationCurrencyKey);
IDirectIntegrationManager.ParameterIntegrationSettings memory usdSettings = _exchangeSettings(msg.sender, sUSD);
(amountReceived, fee, exchangeFeeRate, , , ) = _getAmountsForAtomicExchangeMinusFees(
sourceAmount,
sourceSettings,
destinationSettings,
usdSettings
);
}
/* ========== MUTATIVE FUNCTIONS ========== */
function exchangeAtomically(
address from,
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey,
address destinationAddress,
bytes32 trackingCode,
uint minAmount
) external onlySynthetixorSynth returns (uint amountReceived) {
uint fee;
(amountReceived, fee) = _exchangeAtomically(
from,
sourceCurrencyKey,
sourceAmount,
destinationCurrencyKey,
destinationAddress
);
require(amountReceived >= minAmount, "The amount received is below the minimum amount specified.");
_processTradingRewards(fee, destinationAddress);
if (trackingCode != bytes32(0)) {
_emitTrackingEvent(trackingCode, destinationCurrencyKey, amountReceived, fee);
}
}
/* ========== INTERNAL FUNCTIONS ========== */
function _virtualSynthMastercopy() internal view returns (address) {
return requireAndGetAddress(CONTRACT_VIRTUALSYNTH_MASTERCOPY);
}
function _createVirtualSynth(
IERC20 synth,
address recipient,
uint amount,
bytes32 currencyKey
) internal returns (IVirtualSynth) {
// prevent inverse synths from being allowed due to purgeability
require(currencyKey[0] != 0x69, "Cannot virtualize this synth");
IVirtualSynthInternal vSynth =
IVirtualSynthInternal(_cloneAsMinimalProxy(_virtualSynthMastercopy(), "Could not create new vSynth"));
vSynth.initialize(synth, resolver, recipient, amount, currencyKey);
emit VirtualSynthCreated(address(synth), recipient, address(vSynth), currencyKey, amount);
return IVirtualSynth(address(vSynth));
}
function _exchangeAtomically(
address from,
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey,
address destinationAddress
) internal returns (uint amountReceived, uint fee) {
uint sourceAmountAfterSettlement;
uint exchangeFeeRate;
uint systemSourceRate;
uint systemDestinationRate;
{
IDirectIntegrationManager.ParameterIntegrationSettings memory sourceSettings =
_exchangeSettings(from, sourceCurrencyKey);
IDirectIntegrationManager.ParameterIntegrationSettings memory destinationSettings =
_exchangeSettings(from, destinationCurrencyKey);
if (!_ensureCanExchange(sourceCurrencyKey, destinationCurrencyKey, sourceAmount)) {
return (0, 0);
}
require(!exchangeRates().synthTooVolatileForAtomicExchange(sourceSettings), "Src synth too volatile");
require(!exchangeRates().synthTooVolatileForAtomicExchange(destinationSettings), "Dest synth too volatile");
sourceAmountAfterSettlement = _settleAndCalcSourceAmountRemaining(sourceAmount, from, sourceCurrencyKey);
// If, after settlement the user has no balance left (highly unlikely), then return to prevent
// emitting events of 0 and don't revert so as to ensure the settlement queue is emptied
if (sourceAmountAfterSettlement == 0) {
return (0, 0);
}
// sometimes we need parameters for USD and USD has parameters which could be overridden
IDirectIntegrationManager.ParameterIntegrationSettings memory usdSettings = _exchangeSettings(from, sUSD);
uint systemConvertedAmount;
// Note: also ensures the given synths are allowed to be atomically exchanged
(
amountReceived, // output amount with fee taken out (denominated in dest currency)
fee, // fee amount (denominated in dest currency)
exchangeFeeRate, // applied fee rate
systemConvertedAmount, // current system value without fees (denominated in dest currency)
systemSourceRate, // current system rate for src currency
systemDestinationRate // current system rate for dest currency
) = _getAmountsForAtomicExchangeMinusFees(
sourceAmountAfterSettlement,
sourceSettings,
destinationSettings,
usdSettings
);
// Sanity check atomic output's value against current system value (checking atomic rates)
require(
!circuitBreaker().isDeviationAboveThreshold(systemConvertedAmount, amountReceived.add(fee)),
"Atomic rate deviates too much"
);
// Determine sUSD value of exchange
uint sourceSusdValue;
if (sourceCurrencyKey == sUSD) {
// Use after-settled amount as this is amount converted (not sourceAmount)
sourceSusdValue = sourceAmountAfterSettlement;
} else if (destinationCurrencyKey == sUSD) {
// In this case the systemConvertedAmount would be the fee-free sUSD value of the source synth
sourceSusdValue = systemConvertedAmount;
} else {
// Otherwise, convert source to sUSD value
(uint amountReceivedInUSD, uint sUsdFee, , , , ) =
_getAmountsForAtomicExchangeMinusFees(
sourceAmountAfterSettlement,
sourceSettings,
usdSettings,
usdSettings
);
sourceSusdValue = amountReceivedInUSD.add(sUsdFee);
}
// Check and update atomic volume limit
_checkAndUpdateAtomicVolume(sourceSettings, sourceSusdValue);
}
// Note: We don't need to check their balance as the _convert() below will do a safe subtraction which requires
// the subtraction to not overflow, which would happen if their balance is not sufficient.
_convert(
sourceCurrencyKey,
from,
sourceAmountAfterSettlement,
destinationCurrencyKey,
amountReceived,
destinationAddress,
false // no vsynths
);
// Remit the fee if required
if (fee > 0) {
// Normalize fee to sUSD
// Note: `fee` is being reused to avoid stack too deep errors.
fee = exchangeRates().effectiveValue(destinationCurrencyKey, fee, sUSD);
// Remit the fee in sUSDs
issuer().synths(sUSD).issue(feePool().FEE_ADDRESS(), fee);
// Tell the fee pool about this
feePool().recordFeePaid(fee);
}
// Note: As of this point, `fee` is denominated in sUSD.
// Note: this update of the debt snapshot will not be accurate because the atomic exchange
// was executed with a different rate than the system rate. To be perfect, issuance data,
// priced in system rates, should have been adjusted on the src and dest synth.
// The debt pool is expected to be deprecated soon, and so we don't bother with being
// perfect here. For now, an inaccuracy will slowly accrue over time with increasing atomic
// exchange volume.
_updateSNXIssuedDebtOnExchange(
[sourceCurrencyKey, destinationCurrencyKey],
[systemSourceRate, systemDestinationRate]
);
// Let the DApps know there was a Synth exchange
ISynthetixInternal(address(synthetix())).emitSynthExchange(
from,
sourceCurrencyKey,
sourceAmountAfterSettlement,
destinationCurrencyKey,
amountReceived,
destinationAddress
);
// Emit separate event to track atomic exchanges
ISynthetixInternal(address(synthetix())).emitAtomicSynthExchange(
from,
sourceCurrencyKey,
sourceAmountAfterSettlement,
destinationCurrencyKey,
amountReceived,
destinationAddress
);
// No need to persist any exchange information, as no settlement is required for atomic exchanges
}
function _checkAndUpdateAtomicVolume(
IDirectIntegrationManager.ParameterIntegrationSettings memory settings,
uint sourceSusdValue
) internal {
uint currentVolume =
uint(lastAtomicVolume.time) == block.timestamp
? uint(lastAtomicVolume.volume).add(sourceSusdValue)
: sourceSusdValue;
require(currentVolume <= settings.atomicMaxVolumePerBlock, "Surpassed volume limit");
lastAtomicVolume.time = uint64(block.timestamp);
lastAtomicVolume.volume = uint192(currentVolume); // Protected by volume limit check above
}
function _feeRateForAtomicExchange(
IDirectIntegrationManager.ParameterIntegrationSettings memory sourceSettings,
IDirectIntegrationManager.ParameterIntegrationSettings memory destinationSettings
) internal view returns (uint) {
// Get the exchange fee rate as per source and destination currencyKey
uint baseRate = sourceSettings.atomicExchangeFeeRate.add(destinationSettings.atomicExchangeFeeRate);
if (baseRate == 0) {
// If no atomic rate was set, fallback to the regular exchange rate
baseRate = sourceSettings.exchangeFeeRate.add(destinationSettings.exchangeFeeRate);
}
return baseRate;
}
function _getAmountsForAtomicExchangeMinusFees(
uint sourceAmount,
IDirectIntegrationManager.ParameterIntegrationSettings memory sourceSettings,
IDirectIntegrationManager.ParameterIntegrationSettings memory destinationSettings,
IDirectIntegrationManager.ParameterIntegrationSettings memory usdSettings
)
internal
view
returns (
uint amountReceived,
uint fee,
uint exchangeFeeRate,
uint systemConvertedAmount,
uint systemSourceRate,
uint systemDestinationRate
)
{
uint destinationAmount;
(destinationAmount, systemConvertedAmount, systemSourceRate, systemDestinationRate) = exchangeRates()
.effectiveAtomicValueAndRates(sourceSettings, sourceAmount, destinationSettings, usdSettings);
exchangeFeeRate = _feeRateForAtomicExchange(sourceSettings, destinationSettings);
amountReceived = ExchangeSettlementLib._deductFeesFromAmount(destinationAmount, exchangeFeeRate);
fee = destinationAmount.sub(amountReceived);
}
event VirtualSynthCreated(
address indexed synth,
address indexed recipient,
address vSynth,
bytes32 currencyKey,
uint amount
);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
pragma experimental ABIEncoderV2;
import "./IExchanger.sol";
import "./ICircuitBreaker.sol";
import "./IExchangeRates.sol";
import "./IExchangeState.sol";
import "./IDebtCache.sol";
import "./IIssuer.sol";
import "./ISynthetix.sol";
import "./SafeDecimalMath.sol";
library ExchangeSettlementLib {
using SafeMath for uint256;
using SafeDecimalMath for uint256;
struct ResolvedAddresses {
IExchangeState exchangeState;
IExchangeRates exchangeRates;
ICircuitBreaker circuitBreaker;
IExchangerInternalDebtCache debtCache;
IIssuer issuer;
ISynthetix synthetix;
}
bytes32 internal constant sUSD = "sUSD";
function internalSettle(
ResolvedAddresses calldata resolvedAddresses,
address from,
bytes32 currencyKey,
bool updateCache,
uint waitingPeriod
)
external
returns (
uint reclaimed,
uint refunded,
uint numEntriesSettled
)
{
require(
maxSecsLeftInWaitingPeriod(resolvedAddresses.exchangeState, from, currencyKey, waitingPeriod) == 0,
"Cannot settle during waiting period"
);
(uint reclaimAmount, uint rebateAmount, uint entries, IExchanger.ExchangeEntrySettlement[] memory settlements) =
_settlementOwing(resolvedAddresses, from, currencyKey, waitingPeriod);
if (reclaimAmount > rebateAmount) {
reclaimed = reclaimAmount.sub(rebateAmount);
_reclaim(resolvedAddresses, from, currencyKey, reclaimed);
} else if (rebateAmount > reclaimAmount) {
refunded = rebateAmount.sub(reclaimAmount);
_refund(resolvedAddresses, from, currencyKey, refunded);
}
// by checking a reclaim or refund we also check that the currency key is still a valid synth,
// as the deviation check will return 0 if the synth has been removed.
if (updateCache && (reclaimed > 0 || refunded > 0)) {
bytes32[] memory key = new bytes32[](1);
key[0] = currencyKey;
resolvedAddresses.debtCache.updateCachedSynthDebts(key);
}
// emit settlement event for each settled exchange entry
for (uint i = 0; i < settlements.length; i++) {
emit ExchangeEntrySettled(
from,
settlements[i].src,
settlements[i].amount,
settlements[i].dest,
settlements[i].reclaim,
settlements[i].rebate,
settlements[i].srcRoundIdAtPeriodEnd,
settlements[i].destRoundIdAtPeriodEnd,
settlements[i].timestamp
);
}
numEntriesSettled = entries;
// Now remove all entries, even if no reclaim and no rebate
resolvedAddresses.exchangeState.removeEntries(from, currencyKey);
}
function maxSecsLeftInWaitingPeriod(
IExchangeState exchangeState,
address account,
bytes32 currencyKey,
uint waitingPeriod
) public view returns (uint) {
return _secsLeftInWaitingPeriodForExchange(exchangeState.getMaxTimestamp(account, currencyKey), waitingPeriod);
}
function _secsLeftInWaitingPeriodForExchange(uint timestamp, uint waitingPeriod) internal view returns (uint) {
if (timestamp == 0 || now >= timestamp.add(waitingPeriod)) {
return 0;
}
return timestamp.add(waitingPeriod).sub(now);
}
function _reclaim(
ResolvedAddresses memory resolvedAddresses,
address from,
bytes32 currencyKey,
uint amount
) internal {
// burn amount from user
resolvedAddresses.issuer.synths(currencyKey).burn(from, amount);
ISynthetixInternal(address(resolvedAddresses.synthetix)).emitExchangeReclaim(from, currencyKey, amount);
}
function _refund(
ResolvedAddresses memory resolvedAddresses,
address from,
bytes32 currencyKey,
uint amount
) internal {
// issue amount to user
resolvedAddresses.issuer.synths(currencyKey).issue(from, amount);
ISynthetixInternal(address(resolvedAddresses.synthetix)).emitExchangeRebate(from, currencyKey, amount);
}
function hasWaitingPeriodOrSettlementOwing(
ResolvedAddresses calldata resolvedAddresses,
address account,
bytes32 currencyKey,
uint waitingPeriod
) external view returns (bool) {
if (maxSecsLeftInWaitingPeriod(resolvedAddresses.exchangeState, account, currencyKey, waitingPeriod) != 0) {
return true;
}
(uint reclaimAmount, , , ) = _settlementOwing(resolvedAddresses, account, currencyKey, waitingPeriod);
return reclaimAmount > 0;
}
function settlementOwing(
ResolvedAddresses calldata resolvedAddresses,
address account,
bytes32 currencyKey,
uint waitingPeriod
)
external
view
returns (
uint reclaimAmount,
uint rebateAmount,
uint numEntries,
IExchanger.ExchangeEntrySettlement[] memory
)
{
return _settlementOwing(resolvedAddresses, account, currencyKey, waitingPeriod);
}
// Internal function to aggregate each individual rebate and reclaim entry for a synth
function _settlementOwing(
ResolvedAddresses memory resolvedAddresses,
address account,
bytes32 currencyKey,
uint waitingPeriod
)
internal
view
returns (
uint reclaimAmount,
uint rebateAmount,
uint numEntries,
IExchanger.ExchangeEntrySettlement[] memory
)
{
// Need to sum up all reclaim and rebate amounts for the user and the currency key
numEntries = resolvedAddresses.exchangeState.getLengthOfEntries(account, currencyKey);
// For each unsettled exchange
IExchanger.ExchangeEntrySettlement[] memory settlements = new IExchanger.ExchangeEntrySettlement[](numEntries);
for (uint i = 0; i < numEntries; i++) {
// fetch the entry from storage
IExchangeState.ExchangeEntry memory exchangeEntry =
_getExchangeEntry(resolvedAddresses.exchangeState, account, currencyKey, i);
// determine the last round ids for src and dest pairs when period ended or latest if not over
(uint srcRoundIdAtPeriodEnd, uint destRoundIdAtPeriodEnd) =
_getRoundIdsAtPeriodEnd(resolvedAddresses.exchangeRates, exchangeEntry, waitingPeriod);
// given these round ids, determine what effective value they should have received
uint amountShouldHaveReceived;
{
(uint destinationAmount, , ) =
resolvedAddresses.exchangeRates.effectiveValueAndRatesAtRound(
exchangeEntry.src,
exchangeEntry.amount,
exchangeEntry.dest,
srcRoundIdAtPeriodEnd,
destRoundIdAtPeriodEnd
);
// and deduct the fee from this amount using the exchangeFeeRate from storage
amountShouldHaveReceived = _deductFeesFromAmount(destinationAmount, exchangeEntry.exchangeFeeRate);
}
// SIP-65 settlements where the amount at end of waiting period is beyond the threshold, then
// settle with no reclaim or rebate
bool sip65condition =
resolvedAddresses.circuitBreaker.isDeviationAboveThreshold(
exchangeEntry.amountReceived,
amountShouldHaveReceived
);
uint reclaim;
uint rebate;
if (!sip65condition) {
if (exchangeEntry.amountReceived > amountShouldHaveReceived) {
// if they received more than they should have, add to the reclaim tally
reclaim = exchangeEntry.amountReceived.sub(amountShouldHaveReceived);
reclaimAmount = reclaimAmount.add(reclaim);
} else if (amountShouldHaveReceived > exchangeEntry.amountReceived) {
// if less, add to the rebate tally
rebate = amountShouldHaveReceived.sub(exchangeEntry.amountReceived);
rebateAmount = rebateAmount.add(rebate);
}
}
settlements[i] = IExchanger.ExchangeEntrySettlement({
src: exchangeEntry.src,
amount: exchangeEntry.amount,
dest: exchangeEntry.dest,
reclaim: reclaim,
rebate: rebate,
srcRoundIdAtPeriodEnd: srcRoundIdAtPeriodEnd,
destRoundIdAtPeriodEnd: destRoundIdAtPeriodEnd,
timestamp: exchangeEntry.timestamp
});
}
return (reclaimAmount, rebateAmount, numEntries, settlements);
}
function _getExchangeEntry(
IExchangeState exchangeState,
address account,
bytes32 currencyKey,
uint index
) internal view returns (IExchangeState.ExchangeEntry memory) {
(
bytes32 src,
uint amount,
bytes32 dest,
uint amountReceived,
uint exchangeFeeRate,
uint timestamp,
uint roundIdForSrc,
uint roundIdForDest
) = exchangeState.getEntryAt(account, currencyKey, index);
return
IExchangeState.ExchangeEntry({
src: src,
amount: amount,
dest: dest,
amountReceived: amountReceived,
exchangeFeeRate: exchangeFeeRate,
timestamp: timestamp,
roundIdForSrc: roundIdForSrc,
roundIdForDest: roundIdForDest
});
}
function _getRoundIdsAtPeriodEnd(
IExchangeRates exRates,
IExchangeState.ExchangeEntry memory exchangeEntry,
uint waitingPeriod
) internal view returns (uint srcRoundIdAtPeriodEnd, uint destRoundIdAtPeriodEnd) {
srcRoundIdAtPeriodEnd = exRates.getLastRoundIdBeforeElapsedSecs(
exchangeEntry.src,
exchangeEntry.roundIdForSrc,
exchangeEntry.timestamp,
waitingPeriod
);
destRoundIdAtPeriodEnd = exRates.getLastRoundIdBeforeElapsedSecs(
exchangeEntry.dest,
exchangeEntry.roundIdForDest,
exchangeEntry.timestamp,
waitingPeriod
);
}
function _deductFeesFromAmount(uint destinationAmount, uint exchangeFeeRate)
internal
pure
returns (uint amountReceived)
{
amountReceived = destinationAmount.multiplyDecimal(SafeDecimalMath.unit().sub(exchangeFeeRate));
}
function appendExchange(
ResolvedAddresses calldata resolvedAddresses,
address account,
bytes32 src,
uint amount,
bytes32 dest,
uint amountReceived,
uint exchangeFeeRate
) external {
uint roundIdForSrc = resolvedAddresses.exchangeRates.getCurrentRoundId(src);
uint roundIdForDest = resolvedAddresses.exchangeRates.getCurrentRoundId(dest);
resolvedAddresses.exchangeState.appendExchangeEntry(
account,
src,
amount,
dest,
amountReceived,
exchangeFeeRate,
now,
roundIdForSrc,
roundIdForDest
);
emit ExchangeEntryAppended(
account,
src,
amount,
dest,
amountReceived,
exchangeFeeRate,
roundIdForSrc,
roundIdForDest
);
}
// ========== EVENTS ==========
event ExchangeEntryAppended(
address indexed account,
bytes32 src,
uint256 amount,
bytes32 dest,
uint256 amountReceived,
uint256 exchangeFeeRate,
uint256 roundIdForSrc,
uint256 roundIdForDest
);
event ExchangeEntrySettled(
address indexed from,
bytes32 src,
uint256 amount,
bytes32 dest,
uint256 reclaim,
uint256 rebate,
uint256 srcRoundIdAtPeriodEnd,
uint256 destRoundIdAtPeriodEnd,
uint256 exchangeTimestamp
);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
// Inheritance
import "./Owned.sol";
import "./State.sol";
import "./IExchangeState.sol";
// https://docs.synthetix.io/contracts/source/contracts/exchangestate
contract ExchangeState is Owned, State, IExchangeState {
mapping(address => mapping(bytes32 => IExchangeState.ExchangeEntry[])) public exchanges;
uint public maxEntriesInQueue = 12;
constructor(address _owner, address _associatedContract) public Owned(_owner) State(_associatedContract) {}
/* ========== SETTERS ========== */
function setMaxEntriesInQueue(uint _maxEntriesInQueue) external onlyOwner {
maxEntriesInQueue = _maxEntriesInQueue;
}
/* ========== MUTATIVE FUNCTIONS ========== */
function appendExchangeEntry(
address account,
bytes32 src,
uint amount,
bytes32 dest,
uint amountReceived,
uint exchangeFeeRate,
uint timestamp,
uint roundIdForSrc,
uint roundIdForDest
) external onlyAssociatedContract {
require(exchanges[account][dest].length < maxEntriesInQueue, "Max queue length reached");
exchanges[account][dest].push(
ExchangeEntry({
src: src,
amount: amount,
dest: dest,
amountReceived: amountReceived,
exchangeFeeRate: exchangeFeeRate,
timestamp: timestamp,
roundIdForSrc: roundIdForSrc,
roundIdForDest: roundIdForDest
})
);
}
function removeEntries(address account, bytes32 currencyKey) external onlyAssociatedContract {
delete exchanges[account][currencyKey];
}
/* ========== VIEWS ========== */
function getLengthOfEntries(address account, bytes32 currencyKey) external view returns (uint) {
return exchanges[account][currencyKey].length;
}
function getEntryAt(
address account,
bytes32 currencyKey,
uint index
)
external
view
returns (
bytes32 src,
uint amount,
bytes32 dest,
uint amountReceived,
uint exchangeFeeRate,
uint timestamp,
uint roundIdForSrc,
uint roundIdForDest
)
{
ExchangeEntry storage entry = exchanges[account][currencyKey][index];
return (
entry.src,
entry.amount,
entry.dest,
entry.amountReceived,
entry.exchangeFeeRate,
entry.timestamp,
entry.roundIdForSrc,
entry.roundIdForDest
);
}
function getMaxTimestamp(address account, bytes32 currencyKey) external view returns (uint) {
ExchangeEntry[] storage userEntries = exchanges[account][currencyKey];
uint timestamp = 0;
for (uint i = 0; i < userEntries.length; i++) {
if (userEntries[i].timestamp > timestamp) {
timestamp = userEntries[i].timestamp;
}
}
return timestamp;
}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
// Inheritance
import "./Owned.sol";
import "./Proxyable.sol";
// Libraries
import "./SafeDecimalMath.sol";
// Internal references
import "./TokenState.sol";
// https://docs.synthetix.io/contracts/source/contracts/externstatetoken
contract ExternStateToken is Owned, Proxyable {
using SafeMath for uint;
using SafeDecimalMath for uint;
/* ========== STATE VARIABLES ========== */
/* Stores balances and allowances. */
TokenState public tokenState;
/* Other ERC20 fields. */
string public name;
string public symbol;
uint public totalSupply;
uint8 public decimals;
constructor(
address payable _proxy,
TokenState _tokenState,
string memory _name,
string memory _symbol,
uint _totalSupply,
uint8 _decimals,
address _owner
) public Owned(_owner) Proxyable(_proxy) {
tokenState = _tokenState;
name = _name;
symbol = _symbol;
totalSupply = _totalSupply;
decimals = _decimals;
}
/* ========== VIEWS ========== */
/**
* @notice Returns the ERC20 allowance of one party to spend on behalf of another.
* @param owner The party authorising spending of their funds.
* @param spender The party spending tokenOwner's funds.
*/
function allowance(address owner, address spender) public view returns (uint) {
return tokenState.allowance(owner, spender);
}
/**
* @notice Returns the ERC20 token balance of a given account.
*/
function balanceOf(address account) external view returns (uint) {
return tokenState.balanceOf(account);
}
/* ========== MUTATIVE FUNCTIONS ========== */
/**
* @notice Set the address of the TokenState contract.
* @dev This can be used to "pause" transfer functionality, by pointing the tokenState at 0x000..
* as balances would be unreachable.
*/
function setTokenState(TokenState _tokenState) external optionalProxy_onlyOwner {
tokenState = _tokenState;
emitTokenStateUpdated(address(_tokenState));
}
function _internalTransfer(
address from,
address to,
uint value
) internal returns (bool) {
/* Disallow transfers to irretrievable-addresses. */
require(to != address(0) && to != address(this) && to != address(proxy), "Cannot transfer to this address");
// Insufficient balance will be handled by the safe subtraction.
tokenState.setBalanceOf(from, tokenState.balanceOf(from).sub(value));
tokenState.setBalanceOf(to, tokenState.balanceOf(to).add(value));
// Emit a standard ERC20 transfer event
emitTransfer(from, to, value);
return true;
}
/**
* @dev Perform an ERC20 token transfer. Designed to be called by transfer functions possessing
* the onlyProxy or optionalProxy modifiers.
*/
function _transferByProxy(
address from,
address to,
uint value
) internal returns (bool) {
return _internalTransfer(from, to, value);
}
/*
* @dev Perform an ERC20 token transferFrom. Designed to be called by transferFrom functions
* possessing the optionalProxy or optionalProxy modifiers.
*/
function _transferFromByProxy(
address sender,
address from,
address to,
uint value
) internal returns (bool) {
/* Insufficient allowance will be handled by the safe subtraction. */
tokenState.setAllowance(from, sender, tokenState.allowance(from, sender).sub(value));
return _internalTransfer(from, to, value);
}
/**
* @notice Approves spender to transfer on the message sender's behalf.
*/
function approve(address spender, uint value) public optionalProxy returns (bool) {
address sender = messageSender;
tokenState.setAllowance(sender, spender, value);
emitApproval(sender, spender, value);
return true;
}
/* ========== EVENTS ========== */
function addressToBytes32(address input) internal pure returns (bytes32) {
return bytes32(uint256(uint160(input)));
}
event Transfer(address indexed from, address indexed to, uint value);
bytes32 internal constant TRANSFER_SIG = keccak256("Transfer(address,address,uint256)");
function emitTransfer(
address from,
address to,
uint value
) internal {
proxy._emit(abi.encode(value), 3, TRANSFER_SIG, addressToBytes32(from), addressToBytes32(to), 0);
}
event Approval(address indexed owner, address indexed spender, uint value);
bytes32 internal constant APPROVAL_SIG = keccak256("Approval(address,address,uint256)");
function emitApproval(
address owner,
address spender,
uint value
) internal {
proxy._emit(abi.encode(value), 3, APPROVAL_SIG, addressToBytes32(owner), addressToBytes32(spender), 0);
}
event TokenStateUpdated(address newTokenState);
bytes32 internal constant TOKENSTATEUPDATED_SIG = keccak256("TokenStateUpdated(address)");
function emitTokenStateUpdated(address newTokenState) internal {
proxy._emit(abi.encode(newTokenState), 1, TOKENSTATEUPDATED_SIG, 0, 0, 0);
}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
import "./TradingRewards.sol";
import "./IExchanger.sol";
contract FakeTradingRewards is TradingRewards {
IERC20 public _mockSynthetixToken;
constructor(
address owner,
address periodController,
address resolver,
address mockSynthetixToken
) public TradingRewards(owner, periodController, resolver) {
_mockSynthetixToken = IERC20(mockSynthetixToken);
}
// Synthetix is mocked with an ERC20 token passed via the constructor.
function synthetix() internal view returns (IERC20) {
return IERC20(_mockSynthetixToken);
}
// Return msg.sender so that onlyExchanger modifier can be bypassed.
function exchanger() internal view returns (IExchanger) {
return IExchanger(msg.sender);
}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
// Inheritance
import "./Owned.sol";
import "./Proxyable.sol";
import "./LimitedSetup.sol";
import "./MixinResolver.sol";
import "./MixinSystemSettings.sol";
import "./IFeePool.sol";
// Libraries
import "./SafeDecimalMath.sol";
import "./AggregatorV2V3Interface.sol";
// Internal references
import "./IERC20.sol";
import "./ISynth.sol";
import "./ISystemStatus.sol";
import "./ISynthetix.sol";
import "./ISynthetixDebtShare.sol";
import "./FeePoolEternalStorage.sol";
import "./IExchanger.sol";
import "./IIssuer.sol";
import "./IRewardEscrowV2.sol";
import "./IDelegateApprovals.sol";
import "./IRewardsDistribution.sol";
import "./ICollateralManager.sol";
import "./IEtherWrapper.sol";
import "./IFuturesMarketManager.sol";
import "./IWrapperFactory.sol";
import "./ISynthetixBridgeToOptimism.sol";
// https://docs.synthetix.io/contracts/source/contracts/feepool
contract FeePool is Owned, Proxyable, LimitedSetup, MixinSystemSettings, IFeePool {
using SafeMath for uint;
using SafeDecimalMath for uint;
bytes32 public constant CONTRACT_NAME = "FeePool";
// Where fees are pooled in sUSD.
address public constant FEE_ADDRESS = 0xfeEFEEfeefEeFeefEEFEEfEeFeefEEFeeFEEFEeF;
// sUSD currencyKey. Fees stored and paid in sUSD
bytes32 private sUSD = "sUSD";
// This struct represents the issuance activity that's happened in a fee period.
struct FeePeriod {
uint64 feePeriodId;
uint64 startTime;
uint allNetworksSnxBackedDebt;
uint allNetworksDebtSharesSupply;
uint feesToDistribute;
uint feesClaimed;
uint rewardsToDistribute;
uint rewardsClaimed;
}
// A staker(mintr) can claim from the previous fee period (7 days) only.
// Fee Periods stored and managed from [0], such that [0] is always
// the current active fee period which is not claimable until the
// public function closeCurrentFeePeriod() is called closing the
// current weeks collected fees. [1] is last weeks feeperiod
uint8 public constant FEE_PERIOD_LENGTH = 2;
FeePeriod[FEE_PERIOD_LENGTH] private _recentFeePeriods;
uint256 private _currentFeePeriod;
/* ========== ADDRESS RESOLVER CONFIGURATION ========== */
bytes32 private constant CONTRACT_SYSTEMSTATUS = "SystemStatus";
bytes32 private constant CONTRACT_SYNTHETIXDEBTSHARE = "SynthetixDebtShare";
bytes32 private constant CONTRACT_FEEPOOLETERNALSTORAGE = "FeePoolEternalStorage";
bytes32 private constant CONTRACT_EXCHANGER = "Exchanger";
bytes32 private constant CONTRACT_ISSUER = "Issuer";
bytes32 private constant CONTRACT_REWARDESCROW_V2 = "RewardEscrowV2";
bytes32 private constant CONTRACT_DELEGATEAPPROVALS = "DelegateApprovals";
bytes32 private constant CONTRACT_COLLATERALMANAGER = "CollateralManager";
bytes32 private constant CONTRACT_REWARDSDISTRIBUTION = "RewardsDistribution";
bytes32 private constant CONTRACT_ETHER_WRAPPER = "EtherWrapper";
bytes32 private constant CONTRACT_FUTURES_MARKET_MANAGER = "FuturesMarketManager";
bytes32 private constant CONTRACT_WRAPPER_FACTORY = "WrapperFactory";
bytes32 private constant CONTRACT_SYNTHETIX_BRIDGE_TO_OPTIMISM = "SynthetixBridgeToOptimism";
bytes32 private constant CONTRACT_SYNTHETIX_BRIDGE_TO_BASE = "SynthetixBridgeToBase";
bytes32 private constant CONTRACT_EXT_AGGREGATOR_ISSUED_SYNTHS = "ext:AggregatorIssuedSynths";
bytes32 private constant CONTRACT_EXT_AGGREGATOR_DEBT_RATIO = "ext:AggregatorDebtRatio";
/* ========== ETERNAL STORAGE CONSTANTS ========== */
bytes32 private constant LAST_FEE_WITHDRAWAL = "last_fee_withdrawal";
constructor(
address payable _proxy,
address _owner,
address _resolver
) public Owned(_owner) Proxyable(_proxy) LimitedSetup(3 weeks) MixinSystemSettings(_resolver) {
// Set our initial fee period
_recentFeePeriodsStorage(0).feePeriodId = 1;
_recentFeePeriodsStorage(0).startTime = uint64(block.timestamp);
}
/* ========== VIEWS ========== */
function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
bytes32[] memory existingAddresses = MixinSystemSettings.resolverAddressesRequired();
bytes32[] memory newAddresses = new bytes32[](14);
newAddresses[0] = CONTRACT_SYSTEMSTATUS;
newAddresses[1] = CONTRACT_SYNTHETIXDEBTSHARE;
newAddresses[2] = CONTRACT_FEEPOOLETERNALSTORAGE;
newAddresses[3] = CONTRACT_EXCHANGER;
newAddresses[4] = CONTRACT_ISSUER;
newAddresses[5] = CONTRACT_REWARDESCROW_V2;
newAddresses[6] = CONTRACT_DELEGATEAPPROVALS;
newAddresses[7] = CONTRACT_REWARDSDISTRIBUTION;
newAddresses[8] = CONTRACT_COLLATERALMANAGER;
newAddresses[9] = CONTRACT_WRAPPER_FACTORY;
newAddresses[10] = CONTRACT_ETHER_WRAPPER;
newAddresses[11] = CONTRACT_EXT_AGGREGATOR_ISSUED_SYNTHS;
newAddresses[12] = CONTRACT_EXT_AGGREGATOR_DEBT_RATIO;
newAddresses[13] = CONTRACT_FUTURES_MARKET_MANAGER;
addresses = combineArrays(existingAddresses, newAddresses);
}
function systemStatus() internal view returns (ISystemStatus) {
return ISystemStatus(requireAndGetAddress(CONTRACT_SYSTEMSTATUS));
}
function synthetixDebtShare() internal view returns (ISynthetixDebtShare) {
return ISynthetixDebtShare(requireAndGetAddress(CONTRACT_SYNTHETIXDEBTSHARE));
}
function feePoolEternalStorage() internal view returns (FeePoolEternalStorage) {
return FeePoolEternalStorage(requireAndGetAddress(CONTRACT_FEEPOOLETERNALSTORAGE));
}
function exchanger() internal view returns (IExchanger) {
return IExchanger(requireAndGetAddress(CONTRACT_EXCHANGER));
}
function collateralManager() internal view returns (ICollateralManager) {
return ICollateralManager(requireAndGetAddress(CONTRACT_COLLATERALMANAGER));
}
function issuer() internal view returns (IIssuer) {
return IIssuer(requireAndGetAddress(CONTRACT_ISSUER));
}
function rewardEscrowV2() internal view returns (IRewardEscrowV2) {
return IRewardEscrowV2(requireAndGetAddress(CONTRACT_REWARDESCROW_V2));
}
function delegateApprovals() internal view returns (IDelegateApprovals) {
return IDelegateApprovals(requireAndGetAddress(CONTRACT_DELEGATEAPPROVALS));
}
function rewardsDistribution() internal view returns (IRewardsDistribution) {
return IRewardsDistribution(requireAndGetAddress(CONTRACT_REWARDSDISTRIBUTION));
}
function etherWrapper() internal view returns (IEtherWrapper) {
return IEtherWrapper(requireAndGetAddress(CONTRACT_ETHER_WRAPPER));
}
function futuresMarketManager() internal view returns (IFuturesMarketManager) {
return IFuturesMarketManager(requireAndGetAddress(CONTRACT_FUTURES_MARKET_MANAGER));
}
function wrapperFactory() internal view returns (IWrapperFactory) {
return IWrapperFactory(requireAndGetAddress(CONTRACT_WRAPPER_FACTORY));
}
function issuanceRatio() external view returns (uint) {
return getIssuanceRatio();
}
function feePeriodDuration() external view returns (uint) {
return getFeePeriodDuration();
}
function targetThreshold() external view returns (uint) {
return getTargetThreshold();
}
function allNetworksSnxBackedDebt() public view returns (uint256 debt, uint256 updatedAt) {
(, int256 rawData, , uint timestamp, ) =
AggregatorV2V3Interface(requireAndGetAddress(CONTRACT_EXT_AGGREGATOR_ISSUED_SYNTHS)).latestRoundData();
debt = uint(rawData);
updatedAt = timestamp;
}
function allNetworksDebtSharesSupply() public view returns (uint256 sharesSupply, uint256 updatedAt) {
(, int256 rawIssuedSynths, , uint issuedSynthsUpdatedAt, ) =
AggregatorV2V3Interface(requireAndGetAddress(CONTRACT_EXT_AGGREGATOR_ISSUED_SYNTHS)).latestRoundData();
(, int256 rawRatio, , uint ratioUpdatedAt, ) =
AggregatorV2V3Interface(requireAndGetAddress(CONTRACT_EXT_AGGREGATOR_DEBT_RATIO)).latestRoundData();
uint debt = uint(rawIssuedSynths);
sharesSupply = rawRatio == 0 ? 0 : debt.divideDecimalRoundPrecise(uint(rawRatio));
updatedAt = issuedSynthsUpdatedAt < ratioUpdatedAt ? issuedSynthsUpdatedAt : ratioUpdatedAt;
}
function recentFeePeriods(uint index)
external
view
returns (
uint64 feePeriodId,
uint64 unused, // required post 185 for api compatibility
uint64 startTime,
uint feesToDistribute,
uint feesClaimed,
uint rewardsToDistribute,
uint rewardsClaimed
)
{
FeePeriod memory feePeriod = _recentFeePeriodsStorage(index);
return (
feePeriod.feePeriodId,
0,
feePeriod.startTime,
feePeriod.feesToDistribute,
feePeriod.feesClaimed,
feePeriod.rewardsToDistribute,
feePeriod.rewardsClaimed
);
}
function _recentFeePeriodsStorage(uint index) internal view returns (FeePeriod storage) {
return _recentFeePeriods[(_currentFeePeriod + index) % FEE_PERIOD_LENGTH];
}
/**
* @notice The Exchanger contract informs us when fees are paid.
* @param amount susd amount in fees being paid.
*/
function recordFeePaid(uint amount) external onlyInternalContracts {
// Keep track off fees in sUSD in the open fee pool period.
_recentFeePeriodsStorage(0).feesToDistribute = _recentFeePeriodsStorage(0).feesToDistribute.add(amount);
}
/**
* @notice The RewardsDistribution contract informs us how many SNX rewards are sent to RewardEscrow to be claimed.
*/
function setRewardsToDistribute(uint amount) external optionalProxy {
require(messageSender == address(rewardsDistribution()), "RewardsDistribution only");
// Add the amount of SNX rewards to distribute on top of any rolling unclaimed amount
_recentFeePeriodsStorage(0).rewardsToDistribute = _recentFeePeriodsStorage(0).rewardsToDistribute.add(amount);
}
/**
* @notice Close the current fee period and start a new one.
*/
function closeCurrentFeePeriod() external issuanceActive {
require(getFeePeriodDuration() > 0, "Fee Period Duration not set");
require(_recentFeePeriodsStorage(0).startTime <= (now - getFeePeriodDuration()), "Too early to close fee period");
// get current oracle values
(uint snxBackedDebt, ) = allNetworksSnxBackedDebt();
(uint debtSharesSupply, ) = allNetworksDebtSharesSupply();
// close on this chain
_closeSecondary(snxBackedDebt, debtSharesSupply);
// inform other chain of the chosen values
ISynthetixBridgeToOptimism(
resolver.requireAndGetAddress(
CONTRACT_SYNTHETIX_BRIDGE_TO_OPTIMISM,
"Missing contract: SynthetixBridgeToOptimism"
)
)
.closeFeePeriod(snxBackedDebt, debtSharesSupply);
}
function closeSecondary(uint allNetworksSnxBackedDebt, uint allNetworksDebtSharesSupply) external onlyRelayer {
_closeSecondary(allNetworksSnxBackedDebt, allNetworksDebtSharesSupply);
}
/**
* @notice Close the current fee period and start a new one.
*/
function _closeSecondary(uint allNetworksSnxBackedDebt, uint allNetworksDebtSharesSupply) internal {
etherWrapper().distributeFees();
wrapperFactory().distributeFees();
// before closing the current fee period, set the recorded snxBackedDebt and debtSharesSupply
_recentFeePeriodsStorage(0).allNetworksDebtSharesSupply = allNetworksDebtSharesSupply;
_recentFeePeriodsStorage(0).allNetworksSnxBackedDebt = allNetworksSnxBackedDebt;
// Note: periodClosing is the current period & periodToRollover is the last open claimable period
FeePeriod storage periodClosing = _recentFeePeriodsStorage(0);
FeePeriod storage periodToRollover = _recentFeePeriodsStorage(FEE_PERIOD_LENGTH - 1);
// Any unclaimed fees from the last period in the array roll back one period.
// Because of the subtraction here, they're effectively proportionally redistributed to those who
// have already claimed from the old period, available in the new period.
// The subtraction is important so we don't create a ticking time bomb of an ever growing
// number of fees that can never decrease and will eventually overflow at the end of the fee pool.
_recentFeePeriodsStorage(0).feesToDistribute = periodToRollover
.feesToDistribute
.sub(periodToRollover.feesClaimed)
.add(periodClosing.feesToDistribute);
_recentFeePeriodsStorage(0).rewardsToDistribute = periodToRollover
.rewardsToDistribute
.sub(periodToRollover.rewardsClaimed)
.add(periodClosing.rewardsToDistribute);
// Note: As of SIP-255, all sUSD fee are now automatically burned and are effectively shared amongst stakers in the form of reduced debt.
if (_recentFeePeriodsStorage(0).feesToDistribute > 0) {
issuer().burnSynthsWithoutDebt(sUSD, FEE_ADDRESS, _recentFeePeriodsStorage(0).feesToDistribute);
// Mark the burnt fees as claimed.
_recentFeePeriodsStorage(0).feesClaimed = _recentFeePeriodsStorage(0).feesToDistribute;
}
// Shift the previous fee periods across to make room for the new one.
_currentFeePeriod = _currentFeePeriod.add(FEE_PERIOD_LENGTH).sub(1).mod(FEE_PERIOD_LENGTH);
// Clear the first element of the array to make sure we don't have any stale values.
delete _recentFeePeriods[_currentFeePeriod];
// Open up the new fee period.
// periodID is set to the current timestamp for compatibility with other systems taking snapshots on the debt shares
uint newFeePeriodId = block.timestamp;
_recentFeePeriodsStorage(0).feePeriodId = uint64(newFeePeriodId);
_recentFeePeriodsStorage(0).startTime = uint64(block.timestamp);
// Inform Issuer to start recording for the new fee period
issuer().setCurrentPeriodId(uint128(newFeePeriodId));
emitFeePeriodClosed(_recentFeePeriodsStorage(1).feePeriodId);
}
/**
* @notice Claim fees for last period when available or not already withdrawn.
*/
function claimFees() external issuanceActive optionalProxy returns (bool) {
return _claimFees(messageSender);
}
/**
* @notice Delegated claimFees(). Call from the deletegated address
* and the fees will be sent to the claimingForAddress.
* approveClaimOnBehalf() must be called first to approve the deletage address
* @param claimingForAddress The account you are claiming fees for
*/
function claimOnBehalf(address claimingForAddress) external issuanceActive optionalProxy returns (bool) {
require(delegateApprovals().canClaimFor(claimingForAddress, messageSender), "Not approved to claim on behalf");
return _claimFees(claimingForAddress);
}
/**
* Note: As of SIP-255, all sUSD fees are burned at the closure of the fee period and are no longer claimable.
* @notice Send the rewards to claiming address.
* @param claimingAddress The address to send the rewards to.
*/
function _claimFees(address claimingAddress) internal returns (bool) {
uint rewardsPaid = 0;
uint feesPaid = 0;
uint availableFees;
uint availableRewards;
// Address won't be able to claim fees if it is too far below the target c-ratio.
// It will need to burn synths then try claiming again.
(bool feesClaimable, bool anyRateIsInvalid) = _isFeesClaimableAndAnyRatesInvalid(claimingAddress);
require(feesClaimable, "C-Ratio below penalty threshold");
require(!anyRateIsInvalid, "A synth or SNX rate is invalid");
// Get the claimingAddress available fees and rewards
(availableFees, availableRewards) = feesAvailable(claimingAddress);
require(
availableFees > 0 || availableRewards > 0,
"No fees or rewards available for period, or fees already claimed"
);
// Record the address has claimed for this period
_setLastFeeWithdrawal(claimingAddress, _recentFeePeriodsStorage(1).feePeriodId);
// Mark the fees as paid since they were already burned.
feesPaid = availableFees;
if (availableRewards > 0) {
// Record the reward payment in our recentFeePeriods
rewardsPaid = _recordRewardPayment(availableRewards);
// Send them their rewards
_payRewards(claimingAddress, rewardsPaid);
}
emitFeesClaimed(claimingAddress, feesPaid, rewardsPaid);
return true;
}
/**
* @notice Admin function to import the FeePeriod data from the previous contract
*/
function importFeePeriod(
uint feePeriodIndex,
uint feePeriodId,
uint startTime,
uint feesToDistribute,
uint feesClaimed,
uint rewardsToDistribute,
uint rewardsClaimed
) external optionalProxy_onlyOwner onlyDuringSetup {
require(feePeriodIndex < FEE_PERIOD_LENGTH, "./invalid fee period index");
_recentFeePeriods[feePeriodIndex] = FeePeriod({
feePeriodId: uint64(feePeriodId),
startTime: uint64(startTime),
feesToDistribute: feesToDistribute,
feesClaimed: feesClaimed,
rewardsToDistribute: rewardsToDistribute,
rewardsClaimed: rewardsClaimed,
allNetworksSnxBackedDebt: 0,
allNetworksDebtSharesSupply: 0
});
// make sure recording is aware of the actual period id
if (feePeriodIndex == 0) {
issuer().setCurrentPeriodId(uint128(feePeriodId));
}
}
/**
* @notice Record the reward payment in our recentFeePeriods.
* @param snxAmount The amount of SNX tokens.
*/
function _recordRewardPayment(uint snxAmount) internal returns (uint) {
// Don't assign to the parameter
uint remainingToAllocate = snxAmount;
uint rewardPaid;
// Start at the oldest period and record the amount, moving to newer periods
// until we've exhausted the amount.
// The condition checks for overflow because we're going to 0 with an unsigned int.
for (uint i = FEE_PERIOD_LENGTH - 1; i < FEE_PERIOD_LENGTH; i--) {
uint toDistribute =
_recentFeePeriodsStorage(i).rewardsToDistribute.sub(_recentFeePeriodsStorage(i).rewardsClaimed);
if (toDistribute > 0) {
// Take the smaller of the amount left to claim in the period and the amount we need to allocate
uint amountInPeriod = toDistribute < remainingToAllocate ? toDistribute : remainingToAllocate;
_recentFeePeriodsStorage(i).rewardsClaimed = _recentFeePeriodsStorage(i).rewardsClaimed.add(amountInPeriod);
remainingToAllocate = remainingToAllocate.sub(amountInPeriod);
rewardPaid = rewardPaid.add(amountInPeriod);
// No need to continue iterating if we've recorded the whole amount;
if (remainingToAllocate == 0) return rewardPaid;
}
}
return rewardPaid;
}
/**
* @notice Send the rewards to claiming address - will be locked in rewardEscrow.
* @param account The address to send the fees to.
* @param snxAmount The amount of SNX.
*/
function _payRewards(address account, uint snxAmount) internal notFeeAddress(account) {
/* Escrow the tokens for 1 year. */
uint escrowDuration = 52 weeks;
// Record vesting entry for claiming address and amount
// SNX already minted to rewardEscrow balance
rewardEscrowV2().appendVestingEntry(account, snxAmount, escrowDuration);
}
/**
* @notice The total fees available in the system to be withdrawn in sUSD.
*/
function totalFeesAvailable() external view returns (uint) {
uint totalFees = 0;
// Fees in fee period [0] are not yet available for withdrawal
for (uint i = 1; i < FEE_PERIOD_LENGTH; i++) {
totalFees = totalFees.add(_recentFeePeriodsStorage(i).feesToDistribute);
totalFees = totalFees.sub(_recentFeePeriodsStorage(i).feesClaimed);
}
return totalFees;
}
/**
* @notice The total fees that were already burned (i.e. claimed) in the previous fee period [1].
*/
function totalFeesBurned() external view returns (uint) {
return _recentFeePeriodsStorage(1).feesClaimed;
}
/**
* @notice The total SNX rewards available in the system to be withdrawn
*/
function totalRewardsAvailable() external view returns (uint) {
uint totalRewards = 0;
// Rewards in fee period [0] are not yet available for withdrawal
for (uint i = 1; i < FEE_PERIOD_LENGTH; i++) {
totalRewards = totalRewards.add(_recentFeePeriodsStorage(i).rewardsToDistribute);
totalRewards = totalRewards.sub(_recentFeePeriodsStorage(i).rewardsClaimed);
}
return totalRewards;
}
/**
* @notice The fees available to be withdrawn by a specific account, priced in sUSD
* @dev Returns two amounts, one for fees and one for SNX rewards
*/
function feesAvailable(address account) public view returns (uint, uint) {
// Add up the fees
uint[FEE_PERIOD_LENGTH][2] memory userFees = feesByPeriod(account);
uint totalFees = 0;
uint totalRewards = 0;
// Fees & Rewards in fee period [0] are not yet available for withdrawal
for (uint i = 1; i < FEE_PERIOD_LENGTH; i++) {
totalFees = totalFees.add(userFees[i][0]);
totalRewards = totalRewards.add(userFees[i][1]);
}
// And convert totalFees to sUSD
// Return totalRewards as is in SNX amount
return (totalFees, totalRewards);
}
/**
* @notice The total amount of fees burned for a specific account in the previous period [1].
* Note: Fees in the current fee period [0] are not yet burned.
*/
function feesBurned(address account) public view returns (uint) {
uint[FEE_PERIOD_LENGTH][2] memory userFees = feesByPeriod(account);
return userFees[1][0];
}
/**
* @notice The amount of fees to be burned for an account during the current fee period [0].
* Note: this returns an approximate value based on the current system rate. Any changes in debt shares may affect the outcome of the final amount.
* This also does not consider pending fees in the wrappers since they are distributed at fee period close.
*/
function feesToBurn(address account) public view returns (uint feesFromPeriod) {
ISynthetixDebtShare sds = synthetixDebtShare();
uint userOwnershipPercentage = sds.sharePercent(account);
(feesFromPeriod, ) = _feesAndRewardsFromPeriod(0, userOwnershipPercentage);
return feesFromPeriod;
}
function _isFeesClaimableAndAnyRatesInvalid(address account) internal view returns (bool, bool) {
// Threshold is calculated from ratio % above the target ratio (issuanceRatio).
// 0 < 10%: Claimable
// 10% > above: Unable to claim
(uint ratio, bool anyRateIsInvalid) = issuer().collateralisationRatioAndAnyRatesInvalid(account);
uint targetRatio = getIssuanceRatio();
// Claimable if collateral ratio below target ratio
if (ratio < targetRatio) {
return (true, anyRateIsInvalid);
}
// Calculate the threshold for collateral ratio before fees can't be claimed.
uint ratio_threshold = targetRatio.multiplyDecimal(SafeDecimalMath.unit().add(getTargetThreshold()));
// Not claimable if collateral ratio above threshold
if (ratio > ratio_threshold) {
return (false, anyRateIsInvalid);
}
return (true, anyRateIsInvalid);
}
function isFeesClaimable(address account) external view returns (bool feesClaimable) {
(feesClaimable, ) = _isFeesClaimableAndAnyRatesInvalid(account);
}
/**
* @notice Calculates fees by period for an account, priced in sUSD
* @param account The address you want to query the fees for
*/
function feesByPeriod(address account) public view returns (uint[FEE_PERIOD_LENGTH][2] memory results) {
// What's the user's debt entry index and the debt they owe to the system at current feePeriod
uint userOwnershipPercentage;
ISynthetixDebtShare sds = synthetixDebtShare();
userOwnershipPercentage = sds.sharePercent(account);
// The [0] fee period is not yet ready to claim, but it is a fee period that they can have
// fees owing for, so we need to report on it anyway.
uint feesFromPeriod;
uint rewardsFromPeriod;
(feesFromPeriod, rewardsFromPeriod) = _feesAndRewardsFromPeriod(0, userOwnershipPercentage);
results[0][0] = feesFromPeriod;
results[0][1] = rewardsFromPeriod;
// Retrieve user's last fee claim by periodId
uint lastFeeWithdrawal = getLastFeeWithdrawal(account);
// Go through our fee periods from the oldest feePeriod[FEE_PERIOD_LENGTH - 1] and figure out what we owe them.
// Condition checks for periods > 0
for (uint i = FEE_PERIOD_LENGTH - 1; i > 0; i--) {
uint64 periodId = _recentFeePeriodsStorage(i).feePeriodId;
if (lastFeeWithdrawal < periodId) {
userOwnershipPercentage = sds.sharePercentOnPeriod(account, uint(periodId));
(feesFromPeriod, rewardsFromPeriod) = _feesAndRewardsFromPeriod(i, userOwnershipPercentage);
results[i][0] = feesFromPeriod;
results[i][1] = rewardsFromPeriod;
}
}
}
/**
* @notice ownershipPercentage is a high precision decimals uint based on
* wallet's debtPercentage. Gives a precise amount of the feesToDistribute
* for fees in the period. Precision factor is removed before results are
* returned.
* @dev The reported fees owing for the current period [0] are just a
* running balance until the fee period closes
*/
function _feesAndRewardsFromPeriod(uint period, uint ownershipPercentage) internal view returns (uint, uint) {
// If it's zero, they haven't issued, and they have no fees OR rewards.
if (ownershipPercentage == 0) return (0, 0);
FeePeriod storage fp = _recentFeePeriodsStorage(period);
// Calculate their percentage of the fees / rewards in this period
// This is a high precision integer.
uint feesFromPeriod = fp.feesToDistribute.multiplyDecimal(ownershipPercentage);
uint rewardsFromPeriod = fp.rewardsToDistribute.multiplyDecimal(ownershipPercentage);
return (feesFromPeriod, rewardsFromPeriod);
}
function effectiveDebtRatioForPeriod(address account, uint period) external view returns (uint) {
// if period is not closed yet, or outside of the fee period range, return 0 instead of reverting
if (period == 0 || period >= FEE_PERIOD_LENGTH) {
return 0;
}
// If the period being checked is uninitialised then return 0. This is only at the start of the system.
if (_recentFeePeriodsStorage(period - 1).startTime == 0) return 0;
return synthetixDebtShare().sharePercentOnPeriod(account, uint(_recentFeePeriods[period].feePeriodId));
}
/**
* @notice Get the feePeriodID of the last claim this account made
* @param _claimingAddress account to check the last fee period ID claim for
* @return uint of the feePeriodID this account last claimed
*/
function getLastFeeWithdrawal(address _claimingAddress) public view returns (uint) {
return feePoolEternalStorage().getUIntValue(keccak256(abi.encodePacked(LAST_FEE_WITHDRAWAL, _claimingAddress)));
}
/**
* @notice Calculate the collateral ratio before user is blocked from claiming.
*/
function getPenaltyThresholdRatio() public view returns (uint) {
return getIssuanceRatio().multiplyDecimal(SafeDecimalMath.unit().add(getTargetThreshold()));
}
/**
* @notice Set the feePeriodID of the last claim this account made
* @param _claimingAddress account to set the last feePeriodID claim for
* @param _feePeriodID the feePeriodID this account claimed fees for
*/
function _setLastFeeWithdrawal(address _claimingAddress, uint _feePeriodID) internal {
feePoolEternalStorage().setUIntValue(
keccak256(abi.encodePacked(LAST_FEE_WITHDRAWAL, _claimingAddress)),
_feePeriodID
);
}
/* ========== Modifiers ========== */
function _isInternalContract(address account) internal view returns (bool) {
return
account == address(exchanger()) ||
issuer().synthsByAddress(account) != bytes32(0) ||
collateralManager().hasCollateral(account) ||
account == address(futuresMarketManager()) ||
account == address(wrapperFactory()) ||
account == address(etherWrapper());
}
modifier onlyInternalContracts {
require(_isInternalContract(msg.sender), "Only Internal Contracts");
_;
}
modifier onlyRelayer {
require(
msg.sender == address(this) || msg.sender == resolver.getAddress(CONTRACT_SYNTHETIX_BRIDGE_TO_BASE),
"Only valid relayer can call"
);
_;
}
modifier notFeeAddress(address account) {
require(account != FEE_ADDRESS, "Fee address not allowed");
_;
}
modifier issuanceActive() {
systemStatus().requireIssuanceActive();
_;
}
/* ========== Proxy Events ========== */
event FeePeriodClosed(uint feePeriodId);
bytes32 private constant FEEPERIODCLOSED_SIG = keccak256("FeePeriodClosed(uint256)");
function emitFeePeriodClosed(uint feePeriodId) internal {
proxy._emit(abi.encode(feePeriodId), 1, FEEPERIODCLOSED_SIG, 0, 0, 0);
}
event FeesClaimed(address account, uint sUSDAmount, uint snxRewards);
bytes32 private constant FEESCLAIMED_SIG = keccak256("FeesClaimed(address,uint256,uint256)");
function emitFeesClaimed(
address account,
uint sUSDAmount,
uint snxRewards
) internal {
proxy._emit(abi.encode(account, sUSDAmount, snxRewards), 1, FEESCLAIMED_SIG, 0, 0, 0);
}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
// Inheritance
import "./EternalStorage.sol";
import "./LimitedSetup.sol";
// https://docs.synthetix.io/contracts/source/contracts/feepooleternalstorage
contract FeePoolEternalStorage is EternalStorage, LimitedSetup {
bytes32 internal constant LAST_FEE_WITHDRAWAL = "last_fee_withdrawal";
constructor(address _owner, address _feePool) public EternalStorage(_owner, _feePool) LimitedSetup(6 weeks) {}
function importFeeWithdrawalData(address[] calldata accounts, uint[] calldata feePeriodIDs)
external
onlyOwner
onlyDuringSetup
{
require(accounts.length == feePeriodIDs.length, "Length mismatch");
for (uint8 i = 0; i < accounts.length; i++) {
this.setUIntValue(keccak256(abi.encodePacked(LAST_FEE_WITHDRAWAL, accounts[i])), feePeriodIDs[i]);
}
}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
// Inheritance
import "./Owned.sol";
import "./LimitedSetup.sol";
// Libraries
import "./SafeDecimalMath.sol";
// Internal references
import "./IFeePool.sol";
// https://docs.synthetix.io/contracts/source/contracts/feepoolstate
contract FeePoolState is Owned, LimitedSetup {
using SafeMath for uint;
using SafeDecimalMath for uint;
/* ========== STATE VARIABLES ========== */
uint8 public constant FEE_PERIOD_LENGTH = 6;
address public feePool;
// The IssuanceData activity that's happened in a fee period.
struct IssuanceData {
uint debtPercentage;
uint debtEntryIndex;
}
// The IssuanceData activity that's happened in a fee period.
mapping(address => IssuanceData[FEE_PERIOD_LENGTH]) public accountIssuanceLedger;
constructor(address _owner, IFeePool _feePool) public Owned(_owner) LimitedSetup(6 weeks) {
feePool = address(_feePool);
}
/* ========== SETTERS ========== */
/**
* @notice set the FeePool contract as it is the only authority to be able to call
* appendAccountIssuanceRecord with the onlyFeePool modifer
* @dev Must be set by owner when FeePool logic is upgraded
*/
function setFeePool(IFeePool _feePool) external onlyOwner {
feePool = address(_feePool);
}
/* ========== VIEWS ========== */
/**
* @notice Get an accounts issuanceData for
* @param account users account
* @param index Index in the array to retrieve. Upto FEE_PERIOD_LENGTH
*/
function getAccountsDebtEntry(address account, uint index)
public
view
returns (uint debtPercentage, uint debtEntryIndex)
{
require(index < FEE_PERIOD_LENGTH, "index exceeds the FEE_PERIOD_LENGTH");
debtPercentage = accountIssuanceLedger[account][index].debtPercentage;
debtEntryIndex = accountIssuanceLedger[account][index].debtEntryIndex;
}
/**
* @notice Find the oldest debtEntryIndex for the corresponding closingDebtIndex
* @param account users account
* @param closingDebtIndex the last periods debt index on close
*/
function applicableIssuanceData(address account, uint closingDebtIndex) external view returns (uint, uint) {
IssuanceData[FEE_PERIOD_LENGTH] memory issuanceData = accountIssuanceLedger[account];
// We want to use the user's debtEntryIndex at when the period closed
// Find the oldest debtEntryIndex for the corresponding closingDebtIndex
for (uint i = 0; i < FEE_PERIOD_LENGTH; i++) {
if (closingDebtIndex >= issuanceData[i].debtEntryIndex) {
return (issuanceData[i].debtPercentage, issuanceData[i].debtEntryIndex);
}
}
}
/* ========== MUTATIVE FUNCTIONS ========== */
/**
* @notice Logs an accounts issuance data in the current fee period which is then stored historically
* @param account Message.Senders account address
* @param debtRatio Debt of this account as a percentage of the global debt.
* @param debtEntryIndex The index in the global debt ledger. synthetix.synthetixState().issuanceData(account)
* @param currentPeriodStartDebtIndex The startingDebtIndex of the current fee period
* @dev onlyFeePool to call me on synthetix.issue() & synthetix.burn() calls to store the locked SNX
* per fee period so we know to allocate the correct proportions of fees and rewards per period
accountIssuanceLedger[account][0] has the latest locked amount for the current period. This can be update as many time
accountIssuanceLedger[account][1-2] has the last locked amount for a previous period they minted or burned
*/
function appendAccountIssuanceRecord(
address account,
uint debtRatio,
uint debtEntryIndex,
uint currentPeriodStartDebtIndex
) external onlyFeePool {
// Is the current debtEntryIndex within this fee period
if (accountIssuanceLedger[account][0].debtEntryIndex < currentPeriodStartDebtIndex) {
// If its older then shift the previous IssuanceData entries periods down to make room for the new one.
issuanceDataIndexOrder(account);
}
// Always store the latest IssuanceData entry at [0]
accountIssuanceLedger[account][0].debtPercentage = debtRatio;
accountIssuanceLedger[account][0].debtEntryIndex = debtEntryIndex;
}
/**
* @notice Pushes down the entire array of debt ratios per fee period
*/
function issuanceDataIndexOrder(address account) private {
for (uint i = FEE_PERIOD_LENGTH - 2; i < FEE_PERIOD_LENGTH; i--) {
uint next = i + 1;
accountIssuanceLedger[account][next].debtPercentage = accountIssuanceLedger[account][i].debtPercentage;
accountIssuanceLedger[account][next].debtEntryIndex = accountIssuanceLedger[account][i].debtEntryIndex;
}
}
/**
* @notice Import issuer data from synthetixState.issuerData on FeePeriodClose() block #
* @dev Only callable by the contract owner, and only for 6 weeks after deployment.
* @param accounts Array of issuing addresses
* @param ratios Array of debt ratios
* @param periodToInsert The Fee Period to insert the historical records into
* @param feePeriodCloseIndex An accounts debtEntryIndex is valid when within the fee peroid,
* since the input ratio will be an average of the pervious periods it just needs to be
* > recentFeePeriods[periodToInsert].startingDebtIndex
* < recentFeePeriods[periodToInsert - 1].startingDebtIndex
*/
function importIssuerData(
address[] calldata accounts,
uint[] calldata ratios,
uint periodToInsert,
uint feePeriodCloseIndex
) external onlyOwner onlyDuringSetup {
require(accounts.length == ratios.length, "./Length mismatch");
for (uint i = 0; i < accounts.length; i++) {
accountIssuanceLedger[accounts[i]][periodToInsert].debtPercentage = ratios[i];
accountIssuanceLedger[accounts[i]][periodToInsert].debtEntryIndex = feePeriodCloseIndex;
emit IssuanceDebtRatioEntry(accounts[i], ratios[i], feePeriodCloseIndex);
}
}
/* ========== MODIFIERS ========== */
modifier onlyFeePool {
require(msg.sender == address(feePool), "Only the FeePool contract can perform this action");
_;
}
/* ========== Events ========== */
event IssuanceDebtRatioEntry(address indexed account, uint debtRatio, uint feePeriodCloseIndex);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.5.0;
interface FlagsInterface {
function getFlag(address) external view returns (bool);
function getFlags(address[] calldata) external view returns (bool[] memory);
function raiseFlag(address) external;
function raiseFlags(address[] calldata) external;
function lowerFlags(address[] calldata) external;
function setRaisingAccessController(address) external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
// Inheritance
import "./ContractStorage.sol";
import "./IFlexibleStorage.sol";
// Internal References
import "./IAddressResolver.sol";
// https://docs.synthetix.io/contracts/source/contracts/flexiblestorage
contract FlexibleStorage is ContractStorage, IFlexibleStorage {
mapping(bytes32 => mapping(bytes32 => uint)) internal uintStorage;
mapping(bytes32 => mapping(bytes32 => int)) internal intStorage;
mapping(bytes32 => mapping(bytes32 => address)) internal addressStorage;
mapping(bytes32 => mapping(bytes32 => bool)) internal boolStorage;
mapping(bytes32 => mapping(bytes32 => bytes32)) internal bytes32Storage;
constructor(address _resolver) public ContractStorage(_resolver) {}
/* ========== INTERNAL FUNCTIONS ========== */
function _setUIntValue(
bytes32 contractName,
bytes32 record,
uint value
) internal {
uintStorage[_memoizeHash(contractName)][record] = value;
emit ValueSetUInt(contractName, record, value);
}
function _setIntValue(
bytes32 contractName,
bytes32 record,
int value
) internal {
intStorage[_memoizeHash(contractName)][record] = value;
emit ValueSetInt(contractName, record, value);
}
function _setAddressValue(
bytes32 contractName,
bytes32 record,
address value
) internal {
addressStorage[_memoizeHash(contractName)][record] = value;
emit ValueSetAddress(contractName, record, value);
}
function _setBoolValue(
bytes32 contractName,
bytes32 record,
bool value
) internal {
boolStorage[_memoizeHash(contractName)][record] = value;
emit ValueSetBool(contractName, record, value);
}
function _setBytes32Value(
bytes32 contractName,
bytes32 record,
bytes32 value
) internal {
bytes32Storage[_memoizeHash(contractName)][record] = value;
emit ValueSetBytes32(contractName, record, value);
}
/* ========== VIEWS ========== */
function getUIntValue(bytes32 contractName, bytes32 record) external view returns (uint) {
return uintStorage[hashes[contractName]][record];
}
function getUIntValues(bytes32 contractName, bytes32[] calldata records) external view returns (uint[] memory) {
uint[] memory results = new uint[](records.length);
mapping(bytes32 => uint) storage data = uintStorage[hashes[contractName]];
for (uint i = 0; i < records.length; i++) {
results[i] = data[records[i]];
}
return results;
}
function getIntValue(bytes32 contractName, bytes32 record) external view returns (int) {
return intStorage[hashes[contractName]][record];
}
function getIntValues(bytes32 contractName, bytes32[] calldata records) external view returns (int[] memory) {
int[] memory results = new int[](records.length);
mapping(bytes32 => int) storage data = intStorage[hashes[contractName]];
for (uint i = 0; i < records.length; i++) {
results[i] = data[records[i]];
}
return results;
}
function getAddressValue(bytes32 contractName, bytes32 record) external view returns (address) {
return addressStorage[hashes[contractName]][record];
}
function getAddressValues(bytes32 contractName, bytes32[] calldata records) external view returns (address[] memory) {
address[] memory results = new address[](records.length);
mapping(bytes32 => address) storage data = addressStorage[hashes[contractName]];
for (uint i = 0; i < records.length; i++) {
results[i] = data[records[i]];
}
return results;
}
function getBoolValue(bytes32 contractName, bytes32 record) external view returns (bool) {
return boolStorage[hashes[contractName]][record];
}
function getBoolValues(bytes32 contractName, bytes32[] calldata records) external view returns (bool[] memory) {
bool[] memory results = new bool[](records.length);
mapping(bytes32 => bool) storage data = boolStorage[hashes[contractName]];
for (uint i = 0; i < records.length; i++) {
results[i] = data[records[i]];
}
return results;
}
function getBytes32Value(bytes32 contractName, bytes32 record) external view returns (bytes32) {
return bytes32Storage[hashes[contractName]][record];
}
function getBytes32Values(bytes32 contractName, bytes32[] calldata records) external view returns (bytes32[] memory) {
bytes32[] memory results = new bytes32[](records.length);
mapping(bytes32 => bytes32) storage data = bytes32Storage[hashes[contractName]];
for (uint i = 0; i < records.length; i++) {
results[i] = data[records[i]];
}
return results;
}
/* ========== RESTRICTED FUNCTIONS ========== */
function setUIntValue(
bytes32 contractName,
bytes32 record,
uint value
) external onlyContract(contractName) {
_setUIntValue(contractName, record, value);
}
function setUIntValues(
bytes32 contractName,
bytes32[] calldata records,
uint[] calldata values
) external onlyContract(contractName) {
require(records.length == values.length, "Input lengths must match");
for (uint i = 0; i < records.length; i++) {
_setUIntValue(contractName, records[i], values[i]);
}
}
function deleteUIntValue(bytes32 contractName, bytes32 record) external onlyContract(contractName) {
uint value = uintStorage[hashes[contractName]][record];
emit ValueDeletedUInt(contractName, record, value);
delete uintStorage[hashes[contractName]][record];
}
function setIntValue(
bytes32 contractName,
bytes32 record,
int value
) external onlyContract(contractName) {
_setIntValue(contractName, record, value);
}
function setIntValues(
bytes32 contractName,
bytes32[] calldata records,
int[] calldata values
) external onlyContract(contractName) {
require(records.length == values.length, "Input lengths must match");
for (uint i = 0; i < records.length; i++) {
_setIntValue(contractName, records[i], values[i]);
}
}
function deleteIntValue(bytes32 contractName, bytes32 record) external onlyContract(contractName) {
int value = intStorage[hashes[contractName]][record];
emit ValueDeletedInt(contractName, record, value);
delete intStorage[hashes[contractName]][record];
}
function setAddressValue(
bytes32 contractName,
bytes32 record,
address value
) external onlyContract(contractName) {
_setAddressValue(contractName, record, value);
}
function setAddressValues(
bytes32 contractName,
bytes32[] calldata records,
address[] calldata values
) external onlyContract(contractName) {
require(records.length == values.length, "Input lengths must match");
for (uint i = 0; i < records.length; i++) {
_setAddressValue(contractName, records[i], values[i]);
}
}
function deleteAddressValue(bytes32 contractName, bytes32 record) external onlyContract(contractName) {
address value = addressStorage[hashes[contractName]][record];
emit ValueDeletedAddress(contractName, record, value);
delete addressStorage[hashes[contractName]][record];
}
function setBoolValue(
bytes32 contractName,
bytes32 record,
bool value
) external onlyContract(contractName) {
_setBoolValue(contractName, record, value);
}
function setBoolValues(
bytes32 contractName,
bytes32[] calldata records,
bool[] calldata values
) external onlyContract(contractName) {
require(records.length == values.length, "Input lengths must match");
for (uint i = 0; i < records.length; i++) {
_setBoolValue(contractName, records[i], values[i]);
}
}
function deleteBoolValue(bytes32 contractName, bytes32 record) external onlyContract(contractName) {
bool value = boolStorage[hashes[contractName]][record];
emit ValueDeletedBool(contractName, record, value);
delete boolStorage[hashes[contractName]][record];
}
function setBytes32Value(
bytes32 contractName,
bytes32 record,
bytes32 value
) external onlyContract(contractName) {
_setBytes32Value(contractName, record, value);
}
function setBytes32Values(
bytes32 contractName,
bytes32[] calldata records,
bytes32[] calldata values
) external onlyContract(contractName) {
require(records.length == values.length, "Input lengths must match");
for (uint i = 0; i < records.length; i++) {
_setBytes32Value(contractName, records[i], values[i]);
}
}
function deleteBytes32Value(bytes32 contractName, bytes32 record) external onlyContract(contractName) {
bytes32 value = bytes32Storage[hashes[contractName]][record];
emit ValueDeletedBytes32(contractName, record, value);
delete bytes32Storage[hashes[contractName]][record];
}
/* ========== EVENTS ========== */
event ValueSetUInt(bytes32 contractName, bytes32 record, uint value);
event ValueDeletedUInt(bytes32 contractName, bytes32 record, uint value);
event ValueSetInt(bytes32 contractName, bytes32 record, int value);
event ValueDeletedInt(bytes32 contractName, bytes32 record, int value);
event ValueSetAddress(bytes32 contractName, bytes32 record, address value);
event ValueDeletedAddress(bytes32 contractName, bytes32 record, address value);
event ValueSetBool(bytes32 contractName, bytes32 record, bool value);
event ValueDeletedBool(bytes32 contractName, bytes32 record, bool value);
event ValueSetBytes32(bytes32 contractName, bytes32 record, bytes32 value);
event ValueDeletedBytes32(bytes32 contractName, bytes32 record, bytes32 value);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
// Inheritance
import "./FuturesMarketBase.sol";
import "./MixinFuturesNextPriceOrders.sol";
import "./MixinFuturesViews.sol";
import "./IFuturesMarket.sol";
/*
* Synthetic Futures
* =================
*
* Futures markets allow users leveraged exposure to an asset, long or short.
* A user must post some margin in order to open a futures account, and profits/losses are
* continually tallied against this margin. If a user's margin runs out, then their position is closed
* by a liquidation keeper, which is rewarded with a flat fee extracted from the margin.
*
* The Synthetix debt pool is effectively the counterparty to each trade, so if a particular position
* is in profit, then the debt pool pays by issuing sUSD into their margin account,
* while if the position makes a loss then the debt pool burns sUSD from the margin, reducing the
* debt load in the system.
*
* As the debt pool underwrites all positions, the debt-inflation risk to the system is proportional to the
* long-short skew in the market. It is therefore in the interest of the system to reduce the skew.
* To encourage the minimisation of the skew, each position is charged a funding rate, which increases with
* the size of the skew. The funding rate is charged continuously, and positions on the heavier side of the
* market are charged the current funding rate times the notional value of their position, while positions
* on the lighter side are paid at the same rate to keep their positions open.
* As the funding rate is the same (but negated) on both sides of the market, there is an excess quantity of
* funding being charged, which is collected by the debt pool, and serves to reduce the system debt.
*
* The contract architecture is as follows:
*
* - FuturesMarket.sol: one of these exists per asset. Margin is maintained isolated per market.
* this contract is composed of several mixins: `base` contains all the core logic,
* `nextPrice` contains the next-price order flows, and `views` contains logic
* that is only used by external / manager contracts.
*
* - FuturesMarketManager.sol: the manager keeps track of which markets exist, and is the main window between
* futures markets and the rest of the system. It accumulates the total debt
* over all markets, and issues and burns sUSD on each market's behalf.
*
* - FuturesMarketSettings.sol: Holds the settings for each market in the global FlexibleStorage instance used
* by SystemSettings, and provides an interface to modify these values. Other than
* the base asset, these settings determine the behaviour of each market.
* See that contract for descriptions of the meanings of each setting.
*
* Technical note: internal functions within the FuturesMarket contract assume the following:
*
* - prices passed into them are valid;
*
* - funding has already been recomputed up to the current time (hence unrecorded funding is nil);
*
* - the account being managed was not liquidated in the same transaction;
*/
// https://docs.synthetix.io/contracts/source/contracts/FuturesMarket
contract FuturesMarket is IFuturesMarket, FuturesMarketBase, MixinFuturesNextPriceOrders, MixinFuturesViews {
constructor(
address _resolver,
bytes32 _baseAsset,
bytes32 _marketKey
) public FuturesMarketBase(_resolver, _baseAsset, _marketKey) {}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
// Inheritance
import "./MixinFuturesMarketSettings.sol";
import "./IFuturesMarketBaseTypes.sol";
// Libraries
import "./SafeMath.sol";
import "./SignedSafeMath.sol";
import "./SignedSafeDecimalMath.sol";
import "./SafeDecimalMath.sol";
// Internal references
import "./IExchangeCircuitBreaker.sol";
import "./IExchangeRates.sol";
import "./IExchanger.sol";
import "./ISystemStatus.sol";
import "./IERC20.sol";
/*
* Synthetic Futures
* =================
*
* Futures markets allow users leveraged exposure to an asset, long or short.
* A user must post some margin in order to open a futures account, and profits/losses are
* continually tallied against this margin. If a user's margin runs out, then their position is closed
* by a liquidation keeper, which is rewarded with a flat fee extracted from the margin.
*
* The Synthetix debt pool is effectively the counterparty to each trade, so if a particular position
* is in profit, then the debt pool pays by issuing sUSD into their margin account,
* while if the position makes a loss then the debt pool burns sUSD from the margin, reducing the
* debt load in the system.
*
* As the debt pool underwrites all positions, the debt-inflation risk to the system is proportional to the
* long-short skew in the market. It is therefore in the interest of the system to reduce the skew.
* To encourage the minimisation of the skew, each position is charged a funding rate, which increases with
* the size of the skew. The funding rate is charged continuously, and positions on the heavier side of the
* market are charged the current funding rate times the notional value of their position, while positions
* on the lighter side are paid at the same rate to keep their positions open.
* As the funding rate is the same (but negated) on both sides of the market, there is an excess quantity of
* funding being charged, which is collected by the debt pool, and serves to reduce the system debt.
*
* To combat front-running, the system does not confirm a user's order until the next price is received from
* the oracle. Therefore opening a position is a three stage procedure: depositing margin, submitting an order,
* and waiting for that order to be confirmed. The last transaction is performed by a keeper,
* once a price update is detected.
*
* The contract architecture is as follows:
*
* - FuturesMarket.sol: one of these exists per asset. Margin is maintained isolated per market.
*
* - FuturesMarketManager.sol: the manager keeps track of which markets exist, and is the main window between
* futures markets and the rest of the system. It accumulates the total debt
* over all markets, and issues and burns sUSD on each market's behalf.
*
* - FuturesMarketSettings.sol: Holds the settings for each market in the global FlexibleStorage instance used
* by SystemSettings, and provides an interface to modify these values. Other than
* the base asset, these settings determine the behaviour of each market.
* See that contract for descriptions of the meanings of each setting.
*
* Each futures market and the manager operates behind a proxy, and for efficiency they communicate with one another
* using their underlying implementations.
*
* Technical note: internal functions within the FuturesMarket contract assume the following:
*
* - prices passed into them are valid;
*
* - funding has already been recomputed up to the current time (hence unrecorded funding is nil);
*
* - the account being managed was not liquidated in the same transaction;
*/
interface IFuturesMarketManagerInternal {
function issueSUSD(address account, uint amount) external;
function burnSUSD(address account, uint amount) external returns (uint postReclamationAmount);
function payFee(uint amount) external;
}
// https://docs.synthetix.io/contracts/source/contracts/FuturesMarket
contract FuturesMarketBase is MixinFuturesMarketSettings, IFuturesMarketBaseTypes {
/* ========== LIBRARIES ========== */
using SafeMath for uint;
using SignedSafeMath for int;
using SignedSafeDecimalMath for int;
using SafeDecimalMath for uint;
/* ========== CONSTANTS ========== */
// This is the same unit as used inside `SignedSafeDecimalMath`.
int private constant _UNIT = int(10**uint(18));
//slither-disable-next-line naming-convention
bytes32 internal constant sUSD = "sUSD";
/* ========== STATE VARIABLES ========== */
// The market identifier in the futures system (manager + settings). Multiple markets can co-exist
// for the same asset in order to allow migrations.
bytes32 public marketKey;
// The asset being traded in this market. This should be a valid key into the ExchangeRates contract.
bytes32 public baseAsset;
// The total number of base units in long and short positions.
uint128 public marketSize;
/*
* The net position in base units of the whole market.
* When this is positive, longs outweigh shorts. When it is negative, shorts outweigh longs.
*/
int128 public marketSkew;
/*
* The funding sequence allows constant-time calculation of the funding owed to a given position.
* Each entry in the sequence holds the net funding accumulated per base unit since the market was created.
* Then to obtain the net funding over a particular interval, subtract the start point's sequence entry
* from the end point's sequence entry.
* Positions contain the funding sequence entry at the time they were confirmed; so to compute
* the net funding on a given position, obtain from this sequence the net funding per base unit
* since the position was confirmed and multiply it by the position size.
*/
uint32 public fundingLastRecomputed;
int128[] public fundingSequence;
/*
* Each user's position. Multiple positions can always be merged, so each user has
* only have one position at a time.
*/
mapping(address => Position) public positions;
/*
* This holds the value: sum_{p in positions}{p.margin - p.size * (p.lastPrice + fundingSequence[p.lastFundingIndex])}
* Then marketSkew * (price + _nextFundingEntry()) + _entryDebtCorrection yields the total system debt,
* which is equivalent to the sum of remaining margins in all positions.
*/
int128 internal _entryDebtCorrection;
// This increments for each position; zero reflects a position that does not exist.
uint64 internal _nextPositionId = 1;
// Holds the revert message for each type of error.
mapping(uint8 => string) internal _errorMessages;
/* ---------- Address Resolver Configuration ---------- */
bytes32 internal constant CONTRACT_CIRCUIT_BREAKER = "ExchangeCircuitBreaker";
bytes32 internal constant CONTRACT_EXCHANGER = "Exchanger";
bytes32 internal constant CONTRACT_FUTURESMARKETMANAGER = "FuturesMarketManager";
bytes32 internal constant CONTRACT_FUTURESMARKETSETTINGS = "FuturesMarketSettings";
bytes32 internal constant CONTRACT_SYSTEMSTATUS = "SystemStatus";
// convenience struct for passing params between position modification helper functions
struct TradeParams {
int sizeDelta;
uint price;
uint takerFee;
uint makerFee;
bytes32 trackingCode; // optional tracking code for volume source fee sharing
}
/* ========== CONSTRUCTOR ========== */
constructor(
address _resolver,
bytes32 _baseAsset,
bytes32 _marketKey
) public MixinFuturesMarketSettings(_resolver) {
baseAsset = _baseAsset;
marketKey = _marketKey;
// Initialise the funding sequence with 0 initially accrued, so that the first usable funding index is 1.
fundingSequence.push(0);
// Set up the mapping between error codes and their revert messages.
_errorMessages[uint8(Status.InvalidPrice)] = "Invalid price";
_errorMessages[uint8(Status.PriceOutOfBounds)] = "Price out of acceptable range";
_errorMessages[uint8(Status.CanLiquidate)] = "Position can be liquidated";
_errorMessages[uint8(Status.CannotLiquidate)] = "Position cannot be liquidated";
_errorMessages[uint8(Status.MaxMarketSizeExceeded)] = "Max market size exceeded";
_errorMessages[uint8(Status.MaxLeverageExceeded)] = "Max leverage exceeded";
_errorMessages[uint8(Status.InsufficientMargin)] = "Insufficient margin";
_errorMessages[uint8(Status.NotPermitted)] = "Not permitted by this address";
_errorMessages[uint8(Status.NilOrder)] = "Cannot submit empty order";
_errorMessages[uint8(Status.NoPositionOpen)] = "No position open";
_errorMessages[uint8(Status.PriceTooVolatile)] = "Price too volatile";
}
/* ========== VIEWS ========== */
/* ---------- External Contracts ---------- */
function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
bytes32[] memory existingAddresses = MixinFuturesMarketSettings.resolverAddressesRequired();
bytes32[] memory newAddresses = new bytes32[](5);
newAddresses[0] = CONTRACT_EXCHANGER;
newAddresses[1] = CONTRACT_CIRCUIT_BREAKER;
newAddresses[2] = CONTRACT_FUTURESMARKETMANAGER;
newAddresses[3] = CONTRACT_FUTURESMARKETSETTINGS;
newAddresses[4] = CONTRACT_SYSTEMSTATUS;
addresses = combineArrays(existingAddresses, newAddresses);
}
function _exchangeCircuitBreaker() internal view returns (IExchangeCircuitBreaker) {
return IExchangeCircuitBreaker(requireAndGetAddress(CONTRACT_CIRCUIT_BREAKER));
}
function _exchanger() internal view returns (IExchanger) {
return IExchanger(requireAndGetAddress(CONTRACT_EXCHANGER));
}
function _systemStatus() internal view returns (ISystemStatus) {
return ISystemStatus(requireAndGetAddress(CONTRACT_SYSTEMSTATUS));
}
function _manager() internal view returns (IFuturesMarketManagerInternal) {
return IFuturesMarketManagerInternal(requireAndGetAddress(CONTRACT_FUTURESMARKETMANAGER));
}
function _settings() internal view returns (address) {
return requireAndGetAddress(CONTRACT_FUTURESMARKETSETTINGS);
}
/* ---------- Market Details ---------- */
/*
* The size of the skew relative to the size of the market skew scaler.
* This value can be outside of [-1, 1] values.
* Scaler used for skew is at skewScaleUSD to prevent extreme funding rates for small markets.
*/
function _proportionalSkew(uint price) internal view returns (int) {
// marketSize is in baseAsset units so we need to convert from USD units
require(price > 0, "price can't be zero");
uint skewScaleBaseAsset = _skewScaleUSD(marketKey).divideDecimal(price);
require(skewScaleBaseAsset != 0, "skewScale is zero"); // don't divide by zero
return int(marketSkew).divideDecimal(int(skewScaleBaseAsset));
}
function _currentFundingRate(uint price) internal view returns (int) {
int maxFundingRate = int(_maxFundingRate(marketKey));
// Note the minus sign: funding flows in the opposite direction to the skew.
return _min(_max(-_UNIT, -_proportionalSkew(price)), _UNIT).multiplyDecimal(maxFundingRate);
}
function _unrecordedFunding(uint price) internal view returns (int funding) {
int elapsed = int(block.timestamp.sub(fundingLastRecomputed));
// The current funding rate, rescaled to a percentage per second.
int currentFundingRatePerSecond = _currentFundingRate(price) / 1 days;
return currentFundingRatePerSecond.multiplyDecimal(int(price)).mul(elapsed);
}
/*
* The new entry in the funding sequence, appended when funding is recomputed. It is the sum of the
* last entry and the unrecorded funding, so the sequence accumulates running total over the market's lifetime.
*/
function _nextFundingEntry(uint price) internal view returns (int funding) {
return int(fundingSequence[_latestFundingIndex()]).add(_unrecordedFunding(price));
}
function _netFundingPerUnit(uint startIndex, uint price) internal view returns (int) {
// Compute the net difference between start and end indices.
return _nextFundingEntry(price).sub(fundingSequence[startIndex]);
}
/* ---------- Position Details ---------- */
/*
* Determines whether a change in a position's size would violate the max market value constraint.
*/
function _orderSizeTooLarge(
uint maxSize,
int oldSize,
int newSize
) internal view returns (bool) {
// Allow users to reduce an order no matter the market conditions.
if (_sameSide(oldSize, newSize) && _abs(newSize) <= _abs(oldSize)) {
return false;
}
// Either the user is flipping sides, or they are increasing an order on the same side they're already on;
// we check that the side of the market their order is on would not break the limit.
int newSkew = int(marketSkew).sub(oldSize).add(newSize);
int newMarketSize = int(marketSize).sub(_signedAbs(oldSize)).add(_signedAbs(newSize));
int newSideSize;
if (0 < newSize) {
// long case: marketSize + skew
// = (|longSize| + |shortSize|) + (longSize + shortSize)
// = 2 * longSize
newSideSize = newMarketSize.add(newSkew);
} else {
// short case: marketSize - skew
// = (|longSize| + |shortSize|) - (longSize + shortSize)
// = 2 * -shortSize
newSideSize = newMarketSize.sub(newSkew);
}
// newSideSize still includes an extra factor of 2 here, so we will divide by 2 in the actual condition
if (maxSize < _abs(newSideSize.div(2))) {
return true;
}
return false;
}
function _notionalValue(int positionSize, uint price) internal pure returns (int value) {
return positionSize.multiplyDecimal(int(price));
}
function _profitLoss(Position memory position, uint price) internal pure returns (int pnl) {
int priceShift = int(price).sub(int(position.lastPrice));
return int(position.size).multiplyDecimal(priceShift);
}
function _accruedFunding(Position memory position, uint price) internal view returns (int funding) {
uint lastModifiedIndex = position.lastFundingIndex;
if (lastModifiedIndex == 0) {
return 0; // The position does not exist -- no funding.
}
int net = _netFundingPerUnit(lastModifiedIndex, price);
return int(position.size).multiplyDecimal(net);
}
/*
* The initial margin of a position, plus any PnL and funding it has accrued. The resulting value may be negative.
*/
function _marginPlusProfitFunding(Position memory position, uint price) internal view returns (int) {
int funding = _accruedFunding(position, price);
return int(position.margin).add(_profitLoss(position, price)).add(funding);
}
/*
* The value in a position's margin after a deposit or withdrawal, accounting for funding and profit.
* If the resulting margin would be negative or below the liquidation threshold, an appropriate error is returned.
* If the result is not an error, callers of this function that use it to update a position's margin
* must ensure that this is accompanied by a corresponding debt correction update, as per `_applyDebtCorrection`.
*/
function _recomputeMarginWithDelta(
Position memory position,
uint price,
int marginDelta
) internal view returns (uint margin, Status statusCode) {
int newMargin = _marginPlusProfitFunding(position, price).add(marginDelta);
if (newMargin < 0) {
return (0, Status.InsufficientMargin);
}
uint uMargin = uint(newMargin);
int positionSize = int(position.size);
// minimum margin beyond which position can be liquidated
uint lMargin = _liquidationMargin(positionSize, price);
if (positionSize != 0 && uMargin <= lMargin) {
return (uMargin, Status.CanLiquidate);
}
return (uMargin, Status.Ok);
}
function _remainingMargin(Position memory position, uint price) internal view returns (uint) {
int remaining = _marginPlusProfitFunding(position, price);
// If the margin went past zero, the position should have been liquidated - return zero remaining margin.
return uint(_max(0, remaining));
}
function _accessibleMargin(Position memory position, uint price) internal view returns (uint) {
// Ugly solution to rounding safety: leave up to an extra tenth of a cent in the account/leverage
// This should guarantee that the value returned here can always been withdrawn, but there may be
// a little extra actually-accessible value left over, depending on the position size and margin.
uint milli = uint(_UNIT / 1000);
int maxLeverage = int(_maxLeverage(marketKey).sub(milli));
uint inaccessible = _abs(_notionalValue(position.size, price).divideDecimal(maxLeverage));
// If the user has a position open, we'll enforce a min initial margin requirement.
if (0 < inaccessible) {
uint minInitialMargin = _minInitialMargin();
if (inaccessible < minInitialMargin) {
inaccessible = minInitialMargin;
}
inaccessible = inaccessible.add(milli);
}
uint remaining = _remainingMargin(position, price);
if (remaining <= inaccessible) {
return 0;
}
return remaining.sub(inaccessible);
}
/**
* The fee charged from the margin during liquidation. Fee is proportional to position size
* but is at least the _minKeeperFee() of sUSD to prevent underincentivising
* liquidations of small positions.
* @param positionSize size of position in fixed point decimal baseAsset units
* @param price price of single baseAsset unit in sUSD fixed point decimal units
* @return lFee liquidation fee to be paid to liquidator in sUSD fixed point decimal units
*/
function _liquidationFee(int positionSize, uint price) internal view returns (uint lFee) {
// size * price * fee-ratio
uint proportionalFee = _abs(positionSize).multiplyDecimal(price).multiplyDecimal(_liquidationFeeRatio());
uint minFee = _minKeeperFee();
// max(proportionalFee, minFee) - to prevent not incentivising liquidations enough
return proportionalFee > minFee ? proportionalFee : minFee; // not using _max() helper because it's for signed ints
}
/**
* The minimal margin at which liquidation can happen. Is the sum of liquidationBuffer and liquidationFee
* @param positionSize size of position in fixed point decimal baseAsset units
* @param price price of single baseAsset unit in sUSD fixed point decimal units
* @return lMargin liquidation margin to maintain in sUSD fixed point decimal units
* @dev The liquidation margin contains a buffer that is proportional to the position
* size. The buffer should prevent liquidation happenning at negative margin (due to next price being worse)
* so that stakers would not leak value to liquidators through minting rewards that are not from the
* account's margin.
*/
function _liquidationMargin(int positionSize, uint price) internal view returns (uint lMargin) {
uint liquidationBuffer = _abs(positionSize).multiplyDecimal(price).multiplyDecimal(_liquidationBufferRatio());
return liquidationBuffer.add(_liquidationFee(positionSize, price));
}
function _canLiquidate(Position memory position, uint price) internal view returns (bool) {
// No liquidating empty positions.
if (position.size == 0) {
return false;
}
return _remainingMargin(position, price) <= _liquidationMargin(int(position.size), price);
}
function _currentLeverage(
Position memory position,
uint price,
uint remainingMargin_
) internal pure returns (int leverage) {
// No position is open, or it is ready to be liquidated; leverage goes to nil
if (remainingMargin_ == 0) {
return 0;
}
return _notionalValue(position.size, price).divideDecimal(int(remainingMargin_));
}
function _orderFee(TradeParams memory params, uint dynamicFeeRate) internal view returns (uint fee) {
// usd value of the difference in position
int notionalDiff = params.sizeDelta.multiplyDecimal(int(params.price));
// If the order is submitted on the same side as the skew (increasing it) - the taker fee is charged.
// Otherwise if the order is opposite to the skew, the maker fee is charged.
// the case where the order flips the skew is ignored for simplicity due to being negligible
// in both size of effect and frequency of occurrence
uint staticRate = _sameSide(notionalDiff, marketSkew) ? params.takerFee : params.makerFee;
uint feeRate = staticRate.add(dynamicFeeRate);
return _abs(notionalDiff.multiplyDecimal(int(feeRate)));
}
/// Uses the exchanger to get the dynamic fee (SIP-184) for trading from sUSD to baseAsset
/// this assumes dynamic fee is symmetric in direction of trade.
/// @dev this is a pretty expensive action in terms of execution gas as it queries a lot
/// of past rates from oracle. Shoudn't be much of an issue on a rollup though.
function _dynamicFeeRate() internal view returns (uint feeRate, bool tooVolatile) {
return _exchanger().dynamicFeeRateForExchange(sUSD, baseAsset);
}
function _latestFundingIndex() internal view returns (uint) {
return fundingSequence.length.sub(1); // at least one element is pushed in constructor
}
function _postTradeDetails(Position memory oldPos, TradeParams memory params)
internal
view
returns (
Position memory newPosition,
uint fee,
Status tradeStatus
)
{
// Reverts if the user is trying to submit a size-zero order.
if (params.sizeDelta == 0) {
return (oldPos, 0, Status.NilOrder);
}
// The order is not submitted if the user's existing position needs to be liquidated.
if (_canLiquidate(oldPos, params.price)) {
return (oldPos, 0, Status.CanLiquidate);
}
// get the dynamic fee rate SIP-184
(uint dynamicFeeRate, bool tooVolatile) = _dynamicFeeRate();
if (tooVolatile) {
return (oldPos, 0, Status.PriceTooVolatile);
}
// calculate the total fee for exchange
fee = _orderFee(params, dynamicFeeRate);
// Deduct the fee.
// It is an error if the realised margin minus the fee is negative or subject to liquidation.
(uint newMargin, Status status) = _recomputeMarginWithDelta(oldPos, params.price, -int(fee));
if (_isError(status)) {
return (oldPos, 0, status);
}
// construct new position
Position memory newPos =
Position({
id: oldPos.id,
lastFundingIndex: uint64(_latestFundingIndex()),
margin: uint128(newMargin),
lastPrice: uint128(params.price),
size: int128(int(oldPos.size).add(params.sizeDelta))
});
// always allow to decrease a position, otherwise a margin of minInitialMargin can never
// decrease a position as the price goes against them.
// we also add the paid out fee for the minInitialMargin because otherwise minInitialMargin
// is never the actual minMargin, because the first trade will always deduct
// a fee (so the margin that otherwise would need to be transferred would have to include the future
// fee as well, making the UX and definition of min-margin confusing).
bool positionDecreasing = _sameSide(oldPos.size, newPos.size) && _abs(newPos.size) < _abs(oldPos.size);
if (!positionDecreasing) {
// minMargin + fee <= margin is equivalent to minMargin <= margin - fee
// except that we get a nicer error message if fee > margin, rather than arithmetic overflow.
if (uint(newPos.margin).add(fee) < _minInitialMargin()) {
return (oldPos, 0, Status.InsufficientMargin);
}
}
// check that new position margin is above liquidation margin
// (above, in _recomputeMarginWithDelta() we checked the old position, here we check the new one)
// Liquidation margin is considered without a fee, because it wouldn't make sense to allow
// a trade that will make the position liquidatable.
if (newMargin <= _liquidationMargin(newPos.size, params.price)) {
return (newPos, 0, Status.CanLiquidate);
}
// Check that the maximum leverage is not exceeded when considering new margin including the paid fee.
// The paid fee is considered for the benefit of UX of allowed max leverage, otherwise, the actual
// max leverage is always below the max leverage parameter since the fee paid for a trade reduces the margin.
// We'll allow a little extra headroom for rounding errors.
{
// stack too deep
int leverage = int(newPos.size).multiplyDecimal(int(params.price)).divideDecimal(int(newMargin.add(fee)));
if (_maxLeverage(marketKey).add(uint(_UNIT) / 100) < _abs(leverage)) {
return (oldPos, 0, Status.MaxLeverageExceeded);
}
}
// Check that the order isn't too large for the market.
// Allow a bit of extra value in case of rounding errors.
if (
_orderSizeTooLarge(
uint(int(_maxMarketValueUSD(marketKey).add(100 * uint(_UNIT))).divideDecimal(int(params.price))),
oldPos.size,
newPos.size
)
) {
return (oldPos, 0, Status.MaxMarketSizeExceeded);
}
return (newPos, fee, Status.Ok);
}
/* ---------- Utilities ---------- */
/*
* Absolute value of the input, returned as a signed number.
*/
function _signedAbs(int x) internal pure returns (int) {
return x < 0 ? -x : x;
}
/*
* Absolute value of the input, returned as an unsigned number.
*/
function _abs(int x) internal pure returns (uint) {
return uint(_signedAbs(x));
}
function _max(int x, int y) internal pure returns (int) {
return x < y ? y : x;
}
function _min(int x, int y) internal pure returns (int) {
return x < y ? x : y;
}
// True if and only if two positions a and b are on the same side of the market;
// that is, if they have the same sign, or either of them is zero.
function _sameSide(int a, int b) internal pure returns (bool) {
return (a >= 0) == (b >= 0);
}
/*
* True if and only if the given status indicates an error.
*/
function _isError(Status status) internal pure returns (bool) {
return status != Status.Ok;
}
/*
* Revert with an appropriate message if the first argument is true.
*/
function _revertIfError(bool isError, Status status) internal view {
if (isError) {
revert(_errorMessages[uint8(status)]);
}
}
/*
* Revert with an appropriate message if the input is an error.
*/
function _revertIfError(Status status) internal view {
if (_isError(status)) {
revert(_errorMessages[uint8(status)]);
}
}
/*
* The current base price from the oracle, and whether that price was invalid. Zero prices count as invalid.
* Public because used both externally and internally
*/
function assetPrice() public view returns (uint price, bool invalid) {
(price, invalid) = _exchangeCircuitBreaker().rateWithInvalid(baseAsset);
// Ensure we catch uninitialised rates or suspended state / synth
invalid = invalid || price == 0 || _systemStatus().synthSuspended(baseAsset);
return (price, invalid);
}
/* ========== MUTATIVE FUNCTIONS ========== */
/* ---------- Market Operations ---------- */
/*
* The current base price, reverting if it is invalid, or if system or synth is suspended.
* This is mutative because the circuit breaker stores the last price on every invocation.
*/
function _assetPriceRequireSystemChecks() internal returns (uint) {
// check that futures market isn't suspended, revert with appropriate message
_systemStatus().requireFuturesMarketActive(marketKey); // asset and market may be different
// check that synth is active, and wasn't suspended, revert with appropriate message
_systemStatus().requireSynthActive(baseAsset);
// check if circuit breaker if price is within deviation tolerance and system & synth is active
// note: rateWithBreakCircuit (mutative) is used here instead of rateWithInvalid (view). This is
// despite reverting immediately after if circuit is broken, which may seem silly.
// This is in order to persist last-rate in exchangeCircuitBreaker in the happy case
// because last-rate is what used for measuring the deviation for subsequent trades.
(uint price, bool circuitBroken) = _exchangeCircuitBreaker().rateWithBreakCircuit(baseAsset);
// revert if price is invalid or circuit was broken
// note: we revert here, which means that circuit is not really broken (is not persisted), this is
// because the futures methods and interface are designed for reverts, and do not support no-op
// return values.
_revertIfError(circuitBroken, Status.InvalidPrice);
return price;
}
function _recomputeFunding(uint price) internal returns (uint lastIndex) {
uint sequenceLengthBefore = fundingSequence.length;
int funding = _nextFundingEntry(price);
fundingSequence.push(int128(funding));
fundingLastRecomputed = uint32(block.timestamp);
emit FundingRecomputed(funding, sequenceLengthBefore, fundingLastRecomputed);
return sequenceLengthBefore;
}
/**
* Pushes a new entry to the funding sequence at the current price and funding rate.
* @dev Admin only method accessible to FuturesMarketSettings. This is admin only because:
* - When system parameters change, funding should be recomputed, but system may be paused
* during that time for any reason, so this method needs to work even if system is paused.
* But in that case, it shouldn't be accessible to external accounts.
*/
function recomputeFunding() external returns (uint lastIndex) {
// only FuturesMarketSettings is allowed to use this method
_revertIfError(msg.sender != _settings(), Status.NotPermitted);
// This method is the only mutative method that uses the view _assetPrice()
// and not the mutative _assetPriceRequireSystemChecks() that reverts on system flags.
// This is because this method is used by system settings when changing funding related
// parameters, so needs to function even when system / market is paused. E.g. to facilitate
// market migration.
(uint price, bool invalid) = assetPrice();
// A check for a valid price is still in place, to ensure that a system settings action
// doesn't take place when the price is invalid (e.g. some oracle issue).
require(!invalid, "Invalid price");
return _recomputeFunding(price);
}
/*
* The impact of a given position on the debt correction.
*/
function _positionDebtCorrection(Position memory position) internal view returns (int) {
/**
This method only returns the correction term for the debt calculation of the position, and not it's
debt. This is needed for keeping track of the _marketDebt() in an efficient manner to allow O(1) marketDebt
calculation in _marketDebt().
Explanation of the full market debt calculation from the SIP https://sips.synthetix.io/sips/sip-80/:
The overall market debt is the sum of the remaining margin in all positions. The intuition is that
the debt of a single position is the value withdrawn upon closing that position.
single position remaining margin = initial-margin + profit-loss + accrued-funding =
= initial-margin + q * (price - last-price) + q * funding-accrued-per-unit
= initial-margin + q * price - q * last-price + q * (funding - initial-funding)
Total debt = sum ( position remaining margins )
= sum ( initial-margin + q * price - q * last-price + q * (funding - initial-funding) )
= sum( q * price ) + sum( q * funding ) + sum( initial-margin - q * last-price - q * initial-funding )
= skew * price + skew * funding + sum( initial-margin - q * ( last-price + initial-funding ) )
= skew (price + funding) + sum( initial-margin - q * ( last-price + initial-funding ) )
The last term: sum( initial-margin - q * ( last-price + initial-funding ) ) being the position debt correction
that is tracked with each position change using this method.
The first term and the full debt calculation using current skew, price, and funding is calculated globally in _marketDebt().
*/
return
int(position.margin).sub(
int(position.size).multiplyDecimal(int(position.lastPrice).add(fundingSequence[position.lastFundingIndex]))
);
}
function _marketDebt(uint price) internal view returns (uint) {
// short circuit and also convenient during setup
if (marketSkew == 0 && _entryDebtCorrection == 0) {
// if these are 0, the resulting calculation is necessarily zero as well
return 0;
}
// see comment explaining this calculation in _positionDebtCorrection()
int priceWithFunding = int(price).add(_nextFundingEntry(price));
int totalDebt = int(marketSkew).multiplyDecimal(priceWithFunding).add(_entryDebtCorrection);
return uint(_max(totalDebt, 0));
}
/*
* Alter the debt correction to account for the net result of altering a position.
*/
function _applyDebtCorrection(Position memory newPosition, Position memory oldPosition) internal {
int newCorrection = _positionDebtCorrection(newPosition);
int oldCorrection = _positionDebtCorrection(oldPosition);
_entryDebtCorrection = int128(int(_entryDebtCorrection).add(newCorrection).sub(oldCorrection));
}
function _transferMargin(
int marginDelta,
uint price,
address sender
) internal {
// Transfer no tokens if marginDelta is 0
uint absDelta = _abs(marginDelta);
if (marginDelta > 0) {
// A positive margin delta corresponds to a deposit, which will be burnt from their
// sUSD balance and credited to their margin account.
// Ensure we handle reclamation when burning tokens.
uint postReclamationAmount = _manager().burnSUSD(sender, absDelta);
if (postReclamationAmount != absDelta) {
// If balance was insufficient, the actual delta will be smaller
marginDelta = int(postReclamationAmount);
}
} else if (marginDelta < 0) {
// A negative margin delta corresponds to a withdrawal, which will be minted into
// their sUSD balance, and debited from their margin account.
_manager().issueSUSD(sender, absDelta);
} else {
// Zero delta is a no-op
return;
}
Position storage position = positions[sender];
_updatePositionMargin(position, price, marginDelta);
emit MarginTransferred(sender, marginDelta);
emit PositionModified(position.id, sender, position.margin, position.size, 0, price, _latestFundingIndex(), 0);
}
// updates the stored position margin in place (on the stored position)
function _updatePositionMargin(
Position storage position,
uint price,
int marginDelta
) internal {
Position memory oldPosition = position;
// Determine new margin, ensuring that the result is positive.
(uint margin, Status status) = _recomputeMarginWithDelta(oldPosition, price, marginDelta);
_revertIfError(status);
// Update the debt correction.
int positionSize = position.size;
uint fundingIndex = _latestFundingIndex();
_applyDebtCorrection(
Position(0, uint64(fundingIndex), uint128(margin), uint128(price), int128(positionSize)),
Position(0, position.lastFundingIndex, position.margin, position.lastPrice, int128(positionSize))
);
// Update the account's position with the realised margin.
position.margin = uint128(margin);
// We only need to update their funding/PnL details if they actually have a position open
if (positionSize != 0) {
position.lastPrice = uint128(price);
position.lastFundingIndex = uint64(fundingIndex);
// The user can always decrease their margin if they have no position, or as long as:
// * they have sufficient margin to do so
// * the resulting margin would not be lower than the liquidation margin or min initial margin
// * the resulting leverage is lower than the maximum leverage
if (marginDelta < 0) {
_revertIfError(
(margin < _minInitialMargin()) ||
(margin <= _liquidationMargin(position.size, price)) ||
(_maxLeverage(marketKey) < _abs(_currentLeverage(position, price, margin))),
Status.InsufficientMargin
);
}
}
}
/*
* Alter the amount of margin in a position. A positive input triggers a deposit; a negative one, a
* withdrawal. The margin will be burnt or issued directly into/out of the caller's sUSD wallet.
* Reverts on deposit if the caller lacks a sufficient sUSD balance.
* Reverts on withdrawal if the amount to be withdrawn would expose an open position to liquidation.
*/
function transferMargin(int marginDelta) external {
uint price = _assetPriceRequireSystemChecks();
_recomputeFunding(price);
_transferMargin(marginDelta, price, msg.sender);
}
/*
* Withdraws all accessible margin in a position. This will leave some remaining margin
* in the account if the caller has a position open. Equivalent to `transferMargin(-accessibleMargin(sender))`.
*/
function withdrawAllMargin() external {
address sender = msg.sender;
uint price = _assetPriceRequireSystemChecks();
_recomputeFunding(price);
int marginDelta = -int(_accessibleMargin(positions[sender], price));
_transferMargin(marginDelta, price, sender);
}
function _trade(address sender, TradeParams memory params) internal {
Position storage position = positions[sender];
Position memory oldPosition = position;
// Compute the new position after performing the trade
(Position memory newPosition, uint fee, Status status) = _postTradeDetails(oldPosition, params);
_revertIfError(status);
// Update the aggregated market size and skew with the new order size
marketSkew = int128(int(marketSkew).add(newPosition.size).sub(oldPosition.size));
marketSize = uint128(uint(marketSize).add(_abs(newPosition.size)).sub(_abs(oldPosition.size)));
// Send the fee to the fee pool
if (0 < fee) {
_manager().payFee(fee);
// emit tracking code event
if (params.trackingCode != bytes32(0)) {
emit FuturesTracking(params.trackingCode, baseAsset, marketKey, params.sizeDelta, fee);
}
}
// Update the margin, and apply the resulting debt correction
position.margin = newPosition.margin;
_applyDebtCorrection(newPosition, oldPosition);
// Record the trade
uint64 id = oldPosition.id;
uint fundingIndex = _latestFundingIndex();
if (newPosition.size == 0) {
// If the position is being closed, we no longer need to track these details.
delete position.id;
delete position.size;
delete position.lastPrice;
delete position.lastFundingIndex;
} else {
if (oldPosition.size == 0) {
// New positions get new ids.
id = _nextPositionId;
_nextPositionId += 1;
}
position.id = id;
position.size = newPosition.size;
position.lastPrice = uint128(params.price);
position.lastFundingIndex = uint64(fundingIndex);
}
// emit the modification event
emit PositionModified(
id,
sender,
newPosition.margin,
newPosition.size,
params.sizeDelta,
params.price,
fundingIndex,
fee
);
}
/*
* Adjust the sender's position size.
* Reverts if the resulting position is too large, outside the max leverage, or is liquidating.
*/
function modifyPosition(int sizeDelta) external {
_modifyPosition(sizeDelta, bytes32(0));
}
/*
* Same as modifyPosition, but emits an event with the passed tracking code to
* allow offchain calculations for fee sharing with originating integrations
*/
function modifyPositionWithTracking(int sizeDelta, bytes32 trackingCode) external {
_modifyPosition(sizeDelta, trackingCode);
}
function _modifyPosition(int sizeDelta, bytes32 trackingCode) internal {
uint price = _assetPriceRequireSystemChecks();
_recomputeFunding(price);
_trade(
msg.sender,
TradeParams({
sizeDelta: sizeDelta,
price: price,
takerFee: _takerFee(marketKey),
makerFee: _makerFee(marketKey),
trackingCode: trackingCode
})
);
}
/*
* Submit an order to close a position.
*/
function closePosition() external {
_closePosition(bytes32(0));
}
/// Same as closePosition, but emits an even with the trackingCode for volume source fee sharing
function closePositionWithTracking(bytes32 trackingCode) external {
_closePosition(trackingCode);
}
function _closePosition(bytes32 trackingCode) internal {
int size = positions[msg.sender].size;
_revertIfError(size == 0, Status.NoPositionOpen);
uint price = _assetPriceRequireSystemChecks();
_recomputeFunding(price);
_trade(
msg.sender,
TradeParams({
sizeDelta: -size,
price: price,
takerFee: _takerFee(marketKey),
makerFee: _makerFee(marketKey),
trackingCode: trackingCode
})
);
}
function _liquidatePosition(
address account,
address liquidator,
uint price
) internal {
Position storage position = positions[account];
// get remaining margin for sending any leftover buffer to fee pool
uint remMargin = _remainingMargin(position, price);
// Record updates to market size and debt.
int positionSize = position.size;
uint positionId = position.id;
marketSkew = int128(int(marketSkew).sub(positionSize));
marketSize = uint128(uint(marketSize).sub(_abs(positionSize)));
uint fundingIndex = _latestFundingIndex();
_applyDebtCorrection(
Position(0, uint64(fundingIndex), 0, uint128(price), 0),
Position(0, position.lastFundingIndex, position.margin, position.lastPrice, int128(positionSize))
);
// Close the position itself.
delete positions[account];
// Issue the reward to the liquidator.
uint liqFee = _liquidationFee(positionSize, price);
_manager().issueSUSD(liquidator, liqFee);
emit PositionModified(positionId, account, 0, 0, 0, price, fundingIndex, 0);
emit PositionLiquidated(positionId, account, liquidator, positionSize, price, liqFee);
// Send any positive margin buffer to the fee pool
if (remMargin > liqFee) {
_manager().payFee(remMargin.sub(liqFee));
}
}
/*
* Liquidate a position if its remaining margin is below the liquidation fee. This succeeds if and only if
* `canLiquidate(account)` is true, and reverts otherwise.
* Upon liquidation, the position will be closed, and the liquidation fee minted into the liquidator's account.
*/
function liquidatePosition(address account) external {
uint price = _assetPriceRequireSystemChecks();
_recomputeFunding(price);
_revertIfError(!_canLiquidate(positions[account], price), Status.CannotLiquidate);
_liquidatePosition(account, msg.sender, price);
}
/* ========== EVENTS ========== */
event MarginTransferred(address indexed account, int marginDelta);
event PositionModified(
uint indexed id,
address indexed account,
uint margin,
int size,
int tradeSize,
uint lastPrice,
uint fundingIndex,
uint fee
);
event PositionLiquidated(
uint indexed id,
address indexed account,
address indexed liquidator,
int size,
uint price,
uint fee
);
event FundingRecomputed(int funding, uint index, uint timestamp);
event FuturesTracking(bytes32 indexed trackingCode, bytes32 baseAsset, bytes32 marketKey, int sizeDelta, uint fee);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
pragma experimental ABIEncoderV2;
// Internal references
import "./IFuturesMarket.sol";
import "./IFuturesMarketBaseTypes.sol";
import "./IFuturesMarketManager.sol";
import "./IFuturesMarketSettings.sol";
import "./IAddressResolver.sol";
// https://docs.synthetix.io/contracts/source/contracts/FuturesMarketData
// A utility contract to allow the front end to query market data in a single call.
contract FuturesMarketData {
/* ========== TYPES ========== */
struct FuturesGlobals {
uint minInitialMargin;
uint liquidationFeeRatio;
uint liquidationBufferRatio;
uint minKeeperFee;
}
struct MarketSummary {
address market;
bytes32 asset;
bytes32 key;
uint maxLeverage;
uint price;
uint marketSize;
int marketSkew;
uint marketDebt;
int currentFundingRate;
FeeRates feeRates;
}
struct MarketLimits {
uint maxLeverage;
uint maxMarketValueUSD;
}
struct Sides {
uint long;
uint short;
}
struct MarketSizeDetails {
uint marketSize;
FuturesMarketData.Sides sides;
uint marketDebt;
int marketSkew;
}
struct PriceDetails {
uint price;
bool invalid;
}
struct FundingParameters {
uint maxFundingRate;
uint skewScaleUSD;
}
struct FeeRates {
uint takerFee;
uint makerFee;
uint takerFeeNextPrice;
uint makerFeeNextPrice;
}
struct FundingDetails {
int currentFundingRate;
int unrecordedFunding;
uint fundingLastRecomputed;
}
struct MarketData {
address market;
bytes32 baseAsset;
bytes32 marketKey;
FuturesMarketData.FeeRates feeRates;
FuturesMarketData.MarketLimits limits;
FuturesMarketData.FundingParameters fundingParameters;
FuturesMarketData.MarketSizeDetails marketSizeDetails;
FuturesMarketData.PriceDetails priceDetails;
}
struct PositionData {
IFuturesMarketBaseTypes.Position position;
int notionalValue;
int profitLoss;
int accruedFunding;
uint remainingMargin;
uint accessibleMargin;
uint liquidationPrice;
bool canLiquidatePosition;
}
/* ========== STORAGE VARIABLES ========== */
IAddressResolver public resolverProxy;
/* ========== CONSTRUCTOR ========== */
constructor(IAddressResolver _resolverProxy) public {
resolverProxy = _resolverProxy;
}
/* ========== VIEWS ========== */
function _futuresMarketManager() internal view returns (IFuturesMarketManager) {
return
IFuturesMarketManager(
resolverProxy.requireAndGetAddress("FuturesMarketManager", "Missing FuturesMarketManager Address")
);
}
function _futuresMarketSettings() internal view returns (IFuturesMarketSettings) {
return
IFuturesMarketSettings(
resolverProxy.requireAndGetAddress("FuturesMarketSettings", "Missing FuturesMarketSettings Address")
);
}
function globals() external view returns (FuturesGlobals memory) {
IFuturesMarketSettings settings = _futuresMarketSettings();
return
FuturesGlobals({
minInitialMargin: settings.minInitialMargin(),
liquidationFeeRatio: settings.liquidationFeeRatio(),
liquidationBufferRatio: settings.liquidationBufferRatio(),
minKeeperFee: settings.minKeeperFee()
});
}
function parameters(bytes32 marketKey) external view returns (IFuturesMarketSettings.Parameters memory) {
return _parameters(marketKey);
}
function _parameters(bytes32 marketKey) internal view returns (IFuturesMarketSettings.Parameters memory) {
(
uint takerFee,
uint makerFee,
uint takerFeeNextPrice,
uint makerFeeNextPrice,
uint nextPriceConfirmWindow,
uint maxLeverage,
uint maxMarketValueUSD,
uint maxFundingRate,
uint skewScaleUSD
) = _futuresMarketSettings().parameters(marketKey);
return
IFuturesMarketSettings.Parameters(
takerFee,
makerFee,
takerFeeNextPrice,
makerFeeNextPrice,
nextPriceConfirmWindow,
maxLeverage,
maxMarketValueUSD,
maxFundingRate,
skewScaleUSD
);
}
function _marketSummaries(address[] memory markets) internal view returns (MarketSummary[] memory) {
uint numMarkets = markets.length;
MarketSummary[] memory summaries = new MarketSummary[](numMarkets);
for (uint i; i < numMarkets; i++) {
IFuturesMarket market = IFuturesMarket(markets[i]);
bytes32 marketKey = market.marketKey();
bytes32 baseAsset = market.baseAsset();
IFuturesMarketSettings.Parameters memory params = _parameters(marketKey);
(uint price, ) = market.assetPrice();
(uint debt, ) = market.marketDebt();
summaries[i] = MarketSummary(
address(market),
baseAsset,
marketKey,
params.maxLeverage,
price,
market.marketSize(),
market.marketSkew(),
debt,
market.currentFundingRate(),
FeeRates(params.takerFee, params.makerFee, params.takerFeeNextPrice, params.makerFeeNextPrice)
);
}
return summaries;
}
function marketSummaries(address[] calldata markets) external view returns (MarketSummary[] memory) {
return _marketSummaries(markets);
}
function marketSummariesForKeys(bytes32[] calldata marketKeys) external view returns (MarketSummary[] memory) {
return _marketSummaries(_futuresMarketManager().marketsForKeys(marketKeys));
}
function allMarketSummaries() external view returns (MarketSummary[] memory) {
return _marketSummaries(_futuresMarketManager().allMarkets());
}
function _fundingParameters(IFuturesMarketSettings.Parameters memory params)
internal
pure
returns (FundingParameters memory)
{
return FundingParameters(params.maxFundingRate, params.skewScaleUSD);
}
function _marketSizes(IFuturesMarket market) internal view returns (Sides memory) {
(uint long, uint short) = market.marketSizes();
return Sides(long, short);
}
function _marketDetails(IFuturesMarket market) internal view returns (MarketData memory) {
(uint price, bool invalid) = market.assetPrice();
(uint marketDebt, ) = market.marketDebt();
bytes32 baseAsset = market.baseAsset();
bytes32 marketKey = market.marketKey();
IFuturesMarketSettings.Parameters memory params = _parameters(marketKey);
return
MarketData(
address(market),
baseAsset,
marketKey,
FeeRates(params.takerFee, params.makerFee, params.takerFeeNextPrice, params.makerFeeNextPrice),
MarketLimits(params.maxLeverage, params.maxMarketValueUSD),
_fundingParameters(params),
MarketSizeDetails(market.marketSize(), _marketSizes(market), marketDebt, market.marketSkew()),
PriceDetails(price, invalid)
);
}
function marketDetails(IFuturesMarket market) external view returns (MarketData memory) {
return _marketDetails(market);
}
function marketDetailsForKey(bytes32 marketKey) external view returns (MarketData memory) {
return _marketDetails(IFuturesMarket(_futuresMarketManager().marketForKey(marketKey)));
}
function _position(IFuturesMarket market, address account)
internal
view
returns (IFuturesMarketBaseTypes.Position memory)
{
(
uint64 positionId,
uint64 positionEntryIndex,
uint128 positionMargin,
uint128 positionEntryPrice,
int128 positionSize
) = market.positions(account);
return
IFuturesMarketBaseTypes.Position(
positionId,
positionEntryIndex,
positionMargin,
positionEntryPrice,
positionSize
);
}
function _notionalValue(IFuturesMarket market, address account) internal view returns (int) {
(int value, ) = market.notionalValue(account);
return value;
}
function _profitLoss(IFuturesMarket market, address account) internal view returns (int) {
(int value, ) = market.profitLoss(account);
return value;
}
function _accruedFunding(IFuturesMarket market, address account) internal view returns (int) {
(int value, ) = market.accruedFunding(account);
return value;
}
function _remainingMargin(IFuturesMarket market, address account) internal view returns (uint) {
(uint value, ) = market.remainingMargin(account);
return value;
}
function _accessibleMargin(IFuturesMarket market, address account) internal view returns (uint) {
(uint value, ) = market.accessibleMargin(account);
return value;
}
function _liquidationPrice(IFuturesMarket market, address account) internal view returns (uint) {
(uint liquidationPrice, ) = market.liquidationPrice(account);
return liquidationPrice;
}
function _positionDetails(IFuturesMarket market, address account) internal view returns (PositionData memory) {
return
PositionData(
_position(market, account),
_notionalValue(market, account),
_profitLoss(market, account),
_accruedFunding(market, account),
_remainingMargin(market, account),
_accessibleMargin(market, account),
_liquidationPrice(market, account),
market.canLiquidate(account)
);
}
function positionDetails(IFuturesMarket market, address account) external view returns (PositionData memory) {
return _positionDetails(market, account);
}
function positionDetailsForMarketKey(bytes32 marketKey, address account) external view returns (PositionData memory) {
return _positionDetails(IFuturesMarket(_futuresMarketManager().marketForKey(marketKey)), account);
}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
pragma experimental ABIEncoderV2;
// Inheritance
import "./Owned.sol";
import "./MixinResolver.sol";
import "./IFuturesMarketManager.sol";
// Libraries
import "./SafeMath.sol";
import "./AddressSetLib.sol";
// Internal references
import "./ISynth.sol";
import "./IFeePool.sol";
import "./IExchanger.sol";
import "./IERC20.sol";
// basic views that are expected to be supported by v1 (IFuturesMarket) and v2 (via ProxyPerpsV2)
interface IMarketViews {
function marketKey() external view returns (bytes32);
function baseAsset() external view returns (bytes32);
function marketSize() external view returns (uint128);
function marketSkew() external view returns (int128);
function assetPrice() external view returns (uint price, bool invalid);
function marketDebt() external view returns (uint debt, bool isInvalid);
function currentFundingRate() external view returns (int fundingRate);
// v1 does not have a this so we never call it but this is here for v2.
function currentFundingVelocity() external view returns (int fundingVelocity);
// only supported by PerpsV2 Markets (and implemented in ProxyPerpsV2)
function getAllTargets() external view returns (address[] memory);
}
// https://docs.synthetix.io/contracts/source/contracts/FuturesMarketManager
contract FuturesMarketManager is Owned, MixinResolver, IFuturesMarketManager {
using SafeMath for uint;
using AddressSetLib for AddressSetLib.AddressSet;
/* ========== STATE VARIABLES ========== */
AddressSetLib.AddressSet internal _allMarkets;
AddressSetLib.AddressSet internal _legacyMarkets;
AddressSetLib.AddressSet internal _proxiedMarkets;
mapping(bytes32 => address) public marketForKey;
// PerpsV2 implementations
AddressSetLib.AddressSet internal _implementations;
mapping(address => address[]) internal _marketImplementation;
// PerpsV2 endorsed addresses
AddressSetLib.AddressSet internal _endorsedAddresses;
/* ========== ADDRESS RESOLVER CONFIGURATION ========== */
bytes32 public constant CONTRACT_NAME = "FuturesMarketManager";
bytes32 internal constant SUSD = "sUSD";
bytes32 internal constant CONTRACT_SYNTHSUSD = "SynthsUSD";
bytes32 internal constant CONTRACT_FEEPOOL = "FeePool";
bytes32 internal constant CONTRACT_EXCHANGER = "Exchanger";
/* ========== CONSTRUCTOR ========== */
constructor(address _owner, address _resolver) public Owned(_owner) MixinResolver(_resolver) {}
/* ========== VIEWS ========== */
function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
addresses = new bytes32[](3);
addresses[0] = CONTRACT_SYNTHSUSD;
addresses[1] = CONTRACT_FEEPOOL;
addresses[2] = CONTRACT_EXCHANGER;
}
function _sUSD() internal view returns (ISynth) {
return ISynth(requireAndGetAddress(CONTRACT_SYNTHSUSD));
}
function _feePool() internal view returns (IFeePool) {
return IFeePool(requireAndGetAddress(CONTRACT_FEEPOOL));
}
function _exchanger() internal view returns (IExchanger) {
return IExchanger(requireAndGetAddress(CONTRACT_EXCHANGER));
}
/*
* Returns slices of the list of all markets.
*/
function markets(uint index, uint pageSize) external view returns (address[] memory) {
return _allMarkets.getPage(index, pageSize);
}
/*
* Returns slices of the list of all v1 or v2 (proxied) markets.
*/
function markets(
uint index,
uint pageSize,
bool proxiedMarkets
) external view returns (address[] memory) {
if (proxiedMarkets) {
return _proxiedMarkets.getPage(index, pageSize);
} else {
return _legacyMarkets.getPage(index, pageSize);
}
}
/*
* The number of proxied + legacy markets known to the manager.
*/
function numMarkets() external view returns (uint) {
return _allMarkets.elements.length;
}
/*
* The number of proxied or legacy markets known to the manager.
*/
function numMarkets(bool proxiedMarkets) external view returns (uint) {
if (proxiedMarkets) {
return _proxiedMarkets.elements.length;
} else {
return _legacyMarkets.elements.length;
}
}
/*
* The list of all proxied AND legacy markets.
*/
function allMarkets() public view returns (address[] memory) {
return _allMarkets.getPage(0, _allMarkets.elements.length);
}
/*
* The list of all proxied OR legacy markets.
*/
function allMarkets(bool proxiedMarkets) public view returns (address[] memory) {
if (proxiedMarkets) {
return _proxiedMarkets.getPage(0, _proxiedMarkets.elements.length);
} else {
return _legacyMarkets.getPage(0, _legacyMarkets.elements.length);
}
}
function _marketsForKeys(bytes32[] memory marketKeys) internal view returns (address[] memory) {
uint mMarkets = marketKeys.length;
address[] memory results = new address[](mMarkets);
for (uint i; i < mMarkets; i++) {
results[i] = marketForKey[marketKeys[i]];
}
return results;
}
/*
* The market addresses for a given set of market key strings.
*/
function marketsForKeys(bytes32[] calldata marketKeys) external view returns (address[] memory) {
return _marketsForKeys(marketKeys);
}
/*
* The accumulated debt contribution of all futures markets.
*/
function totalDebt() external view returns (uint debt, bool isInvalid) {
uint total;
bool anyIsInvalid;
uint numOfMarkets = _allMarkets.elements.length;
for (uint i = 0; i < numOfMarkets; i++) {
(uint marketDebt, bool invalid) = IMarketViews(_allMarkets.elements[i]).marketDebt();
total = total.add(marketDebt);
anyIsInvalid = anyIsInvalid || invalid;
}
return (total, anyIsInvalid);
}
struct MarketSummary {
address market;
bytes32 asset;
bytes32 marketKey;
uint price;
uint marketSize;
int marketSkew;
uint marketDebt;
int currentFundingRate;
int currentFundingVelocity;
bool priceInvalid;
bool proxied;
}
function _marketSummaries(address[] memory addresses) internal view returns (MarketSummary[] memory) {
uint nMarkets = addresses.length;
MarketSummary[] memory summaries = new MarketSummary[](nMarkets);
for (uint i; i < nMarkets; i++) {
IMarketViews market = IMarketViews(addresses[i]);
bytes32 marketKey = market.marketKey();
bytes32 baseAsset = market.baseAsset();
(uint price, bool invalid) = market.assetPrice();
(uint debt, ) = market.marketDebt();
bool proxied = _proxiedMarkets.contains(addresses[i]);
summaries[i] = MarketSummary({
market: address(market),
asset: baseAsset,
marketKey: marketKey,
price: price,
marketSize: market.marketSize(),
marketSkew: market.marketSkew(),
marketDebt: debt,
currentFundingRate: market.currentFundingRate(),
currentFundingVelocity: proxied ? market.currentFundingVelocity() : 0, // v1 does not have velocity.
priceInvalid: invalid,
proxied: proxied
});
}
return summaries;
}
function marketSummaries(address[] calldata addresses) external view returns (MarketSummary[] memory) {
return _marketSummaries(addresses);
}
function marketSummariesForKeys(bytes32[] calldata marketKeys) external view returns (MarketSummary[] memory) {
return _marketSummaries(_marketsForKeys(marketKeys));
}
function allMarketSummaries() external view returns (MarketSummary[] memory) {
return _marketSummaries(allMarkets());
}
function allEndorsedAddresses() external view returns (address[] memory) {
return _endorsedAddresses.getPage(0, _endorsedAddresses.elements.length);
}
function isEndorsed(address account) external view returns (bool) {
return _endorsedAddresses.contains(account);
}
/* ========== MUTATIVE FUNCTIONS ========== */
function _addImplementations(address market) internal {
address[] memory implementations = IMarketViews(market).getAllTargets();
for (uint i = 0; i < implementations.length; i++) {
_implementations.add(implementations[i]);
}
_marketImplementation[market] = implementations;
}
function _removeImplementations(address market) internal {
address[] memory implementations = _marketImplementation[market];
for (uint i = 0; i < implementations.length; i++) {
if (_implementations.contains(implementations[i])) {
_implementations.remove(implementations[i]);
}
}
delete _marketImplementation[market];
}
/*
* Add a set of new markets. Reverts if some market key already has a market.
*/
function addMarkets(address[] calldata marketsToAdd) external onlyOwner {
uint numOfMarkets = marketsToAdd.length;
for (uint i; i < numOfMarkets; i++) {
_addMarket(marketsToAdd[i], false);
}
}
/*
* Add a set of new markets. Reverts if some market key already has a market.
*/
function addProxiedMarkets(address[] calldata marketsToAdd) external onlyOwner {
uint numOfMarkets = marketsToAdd.length;
for (uint i; i < numOfMarkets; i++) {
_addMarket(marketsToAdd[i], true);
}
}
/*
* Add a set of new markets. Reverts if some market key already has a market.
*/
function _addMarket(address market, bool isProxied) internal onlyOwner {
require(!_allMarkets.contains(market), "Market already exists");
bytes32 key = IMarketViews(market).marketKey();
bytes32 baseAsset = IMarketViews(market).baseAsset();
require(marketForKey[key] == address(0), "Market already exists for key");
marketForKey[key] = market;
_allMarkets.add(market);
if (isProxied) {
_proxiedMarkets.add(market);
// if PerpsV2 market => add implementations
_addImplementations(market);
} else {
_legacyMarkets.add(market);
}
// Emit the event
emit MarketAdded(market, baseAsset, key);
}
function _removeMarkets(address[] memory marketsToRemove) internal {
uint numOfMarkets = marketsToRemove.length;
for (uint i; i < numOfMarkets; i++) {
address market = marketsToRemove[i];
require(market != address(0), "Unknown market");
bytes32 key = IMarketViews(market).marketKey();
bytes32 baseAsset = IMarketViews(market).baseAsset();
require(marketForKey[key] != address(0), "Unknown market");
// if PerpsV2 market => remove implementations
if (_proxiedMarkets.contains(market)) {
_removeImplementations(market);
_proxiedMarkets.remove(market);
} else {
_legacyMarkets.remove(market);
}
delete marketForKey[key];
_allMarkets.remove(market);
emit MarketRemoved(market, baseAsset, key);
}
}
/*
* Remove a set of markets. Reverts if any market is not known to the manager.
*/
function removeMarkets(address[] calldata marketsToRemove) external onlyOwner {
return _removeMarkets(marketsToRemove);
}
/*
* Remove the markets for a given set of market keys. Reverts if any key has no associated market.
*/
function removeMarketsByKey(bytes32[] calldata marketKeysToRemove) external onlyOwner {
_removeMarkets(_marketsForKeys(marketKeysToRemove));
}
function updateMarketsImplementations(address[] calldata marketsToUpdate) external onlyOwner {
uint numOfMarkets = marketsToUpdate.length;
for (uint i; i < numOfMarkets; i++) {
address market = marketsToUpdate[i];
require(market != address(0), "Invalid market");
require(_allMarkets.contains(market), "Unknown market");
// Remove old implementations
_removeImplementations(market);
// Pull new implementations
_addImplementations(market);
}
}
/*
* Allows a market to issue sUSD to an account when it withdraws margin.
* This function is not callable through the proxy, only underlying contracts interact;
* it reverts if not called by a known market.
*/
function issueSUSD(address account, uint amount) external onlyMarketImplementations {
// No settlement is required to issue synths into the target account.
_sUSD().issue(account, amount);
}
/*
* Allows a market to burn sUSD from an account when it deposits margin.
* This function is not callable through the proxy, only underlying contracts interact;
* it reverts if not called by a known market.
*/
function burnSUSD(address account, uint amount) external onlyMarketImplementations returns (uint postReclamationAmount) {
// We'll settle first, in order to ensure the user has sufficient balance.
// If the settlement reduces the user's balance below the requested amount,
// the settled remainder will be the resulting deposit.
// Exchanger.settle ensures synth is active
ISynth sUSD = _sUSD();
(uint reclaimed, , ) = _exchanger().settle(account, SUSD);
uint balanceAfter = amount;
if (0 < reclaimed) {
balanceAfter = IERC20(address(sUSD)).balanceOf(account);
}
// Reduce the value to burn if balance is insufficient after reclamation
amount = balanceAfter < amount ? balanceAfter : amount;
sUSD.burn(account, amount);
return amount;
}
/**
* Allows markets to issue exchange fees into the fee pool and notify it that this occurred.
* This function is not callable through the proxy, only underlying contracts interact;
* it reverts if not called by a known market.
*/
function payFee(uint amount, bytes32 trackingCode) external onlyMarketImplementations {
_payFee(amount, trackingCode);
}
// backwards compatibility with futures v1
function payFee(uint amount) external onlyMarketImplementations {
_payFee(amount, bytes32(0));
}
function _payFee(uint amount, bytes32 trackingCode) internal {
delete trackingCode; // unused for now, will be used SIP 203
IFeePool pool = _feePool();
_sUSD().issue(pool.FEE_ADDRESS(), amount);
pool.recordFeePaid(amount);
}
/*
* Removes a group of endorsed addresses.
* For each address, if it's present is removed, if it's not present it does nothing
*/
function removeEndorsedAddresses(address[] calldata addresses) external onlyOwner {
for (uint i = 0; i < addresses.length; i++) {
if (_endorsedAddresses.contains(addresses[i])) {
_endorsedAddresses.remove(addresses[i]);
emit EndorsedAddressRemoved(addresses[i]);
}
}
}
/*
* Adds a group of endorsed addresses.
* For each address, if it's not present it is added, if it's already present it does nothing
*/
function addEndorsedAddresses(address[] calldata addresses) external onlyOwner {
for (uint i = 0; i < addresses.length; i++) {
_endorsedAddresses.add(addresses[i]);
emit EndorsedAddressAdded(addresses[i]);
}
}
/* ========== MODIFIERS ========== */
function _requireIsMarketOrImplementation() internal view {
require(
_legacyMarkets.contains(msg.sender) || _implementations.contains(msg.sender),
"Permitted only for market implementations"
);
}
modifier onlyMarketImplementations() {
_requireIsMarketOrImplementation();
_;
}
/* ========== EVENTS ========== */
event MarketAdded(address market, bytes32 indexed asset, bytes32 indexed marketKey);
event MarketRemoved(address market, bytes32 indexed asset, bytes32 indexed marketKey);
event EndorsedAddressAdded(address endorsedAddress);
event EndorsedAddressRemoved(address endorsedAddress);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
// Inheritance
import "./Owned.sol";
import "./MixinFuturesMarketSettings.sol";
// Internal references
import "./IFuturesMarketSettings.sol";
import "./IFuturesMarketManager.sol";
import "./IFuturesMarket.sol";
// https://docs.synthetix.io/contracts/source/contracts/FuturesMarketSettings
contract FuturesMarketSettings is Owned, MixinFuturesMarketSettings, IFuturesMarketSettings {
/* ========== CONSTANTS ========== */
/* ---------- Address Resolver Configuration ---------- */
bytes32 internal constant CONTRACT_FUTURES_MARKET_MANAGER = "FuturesMarketManager";
/* ========== CONSTRUCTOR ========== */
constructor(address _owner, address _resolver) public Owned(_owner) MixinFuturesMarketSettings(_resolver) {}
/* ========== VIEWS ========== */
function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
bytes32[] memory existingAddresses = MixinFuturesMarketSettings.resolverAddressesRequired();
bytes32[] memory newAddresses = new bytes32[](1);
newAddresses[0] = CONTRACT_FUTURES_MARKET_MANAGER;
addresses = combineArrays(existingAddresses, newAddresses);
}
function _futuresMarketManager() internal view returns (IFuturesMarketManager) {
return IFuturesMarketManager(requireAndGetAddress(CONTRACT_FUTURES_MARKET_MANAGER));
}
/* ---------- Getters ---------- */
/*
* The fee charged when opening a position on the heavy side of a futures market.
*/
function takerFee(bytes32 _marketKey) external view returns (uint) {
return _takerFee(_marketKey);
}
/*
* The fee charged when opening a position on the light side of a futures market.
*/
function makerFee(bytes32 _marketKey) public view returns (uint) {
return _makerFee(_marketKey);
}
/*
* The fee charged when opening a position on the heavy side of a futures market using next price mechanism.
*/
function takerFeeNextPrice(bytes32 _marketKey) external view returns (uint) {
return _takerFeeNextPrice(_marketKey);
}
/*
* The fee charged when opening a position on the light side of a futures market using next price mechanism.
*/
function makerFeeNextPrice(bytes32 _marketKey) public view returns (uint) {
return _makerFeeNextPrice(_marketKey);
}
/*
* The number of price update rounds during which confirming next-price is allowed
*/
function nextPriceConfirmWindow(bytes32 _marketKey) public view returns (uint) {
return _nextPriceConfirmWindow(_marketKey);
}
/*
* The maximum allowable leverage in a market.
*/
function maxLeverage(bytes32 _marketKey) public view returns (uint) {
return _maxLeverage(_marketKey);
}
/*
* The maximum allowable notional value on each side of a market.
*/
function maxMarketValueUSD(bytes32 _marketKey) public view returns (uint) {
return _maxMarketValueUSD(_marketKey);
}
/*
* The maximum theoretical funding rate per day charged by a market.
*/
function maxFundingRate(bytes32 _marketKey) public view returns (uint) {
return _maxFundingRate(_marketKey);
}
/*
* The skew level at which the max funding rate will be charged.
*/
function skewScaleUSD(bytes32 _marketKey) public view returns (uint) {
return _skewScaleUSD(_marketKey);
}
function parameters(bytes32 _marketKey)
external
view
returns (
uint takerFee,
uint makerFee,
uint takerFeeNextPrice,
uint makerFeeNextPrice,
uint nextPriceConfirmWindow,
uint maxLeverage,
uint maxMarketValueUSD,
uint maxFundingRate,
uint skewScaleUSD
)
{
takerFee = _takerFee(_marketKey);
makerFee = _makerFee(_marketKey);
takerFeeNextPrice = _takerFeeNextPrice(_marketKey);
makerFeeNextPrice = _makerFeeNextPrice(_marketKey);
nextPriceConfirmWindow = _nextPriceConfirmWindow(_marketKey);
maxLeverage = _maxLeverage(_marketKey);
maxMarketValueUSD = _maxMarketValueUSD(_marketKey);
maxFundingRate = _maxFundingRate(_marketKey);
skewScaleUSD = _skewScaleUSD(_marketKey);
}
/*
* The minimum amount of sUSD paid to a liquidator when they successfully liquidate a position.
* This quantity must be no greater than `minInitialMargin`.
*/
function minKeeperFee() external view returns (uint) {
return _minKeeperFee();
}
/*
* Liquidation fee basis points paid to liquidator.
* Use together with minKeeperFee() to calculate the actual fee paid.
*/
function liquidationFeeRatio() external view returns (uint) {
return _liquidationFeeRatio();
}
/*
* Liquidation price buffer in basis points to prevent negative margin on liquidation.
*/
function liquidationBufferRatio() external view returns (uint) {
return _liquidationBufferRatio();
}
/*
* The minimum margin required to open a position.
* This quantity must be no less than `minKeeperFee`.
*/
function minInitialMargin() external view returns (uint) {
return _minInitialMargin();
}
/* ========== MUTATIVE FUNCTIONS ========== */
/* ---------- Setters --------- */
function _setParameter(
bytes32 _marketKey,
bytes32 key,
uint value
) internal {
_flexibleStorage().setUIntValue(SETTING_CONTRACT_NAME, keccak256(abi.encodePacked(_marketKey, key)), value);
emit ParameterUpdated(_marketKey, key, value);
}
function setTakerFee(bytes32 _marketKey, uint _takerFee) public onlyOwner {
require(_takerFee <= 1e18, "taker fee greater than 1");
_setParameter(_marketKey, PARAMETER_TAKER_FEE, _takerFee);
}
function setMakerFee(bytes32 _marketKey, uint _makerFee) public onlyOwner {
require(_makerFee <= 1e18, "maker fee greater than 1");
_setParameter(_marketKey, PARAMETER_MAKER_FEE, _makerFee);
}
function setTakerFeeNextPrice(bytes32 _marketKey, uint _takerFeeNextPrice) public onlyOwner {
require(_takerFeeNextPrice <= 1e18, "taker fee greater than 1");
_setParameter(_marketKey, PARAMETER_TAKER_FEE_NEXT_PRICE, _takerFeeNextPrice);
}
function setMakerFeeNextPrice(bytes32 _marketKey, uint _makerFeeNextPrice) public onlyOwner {
require(_makerFeeNextPrice <= 1e18, "maker fee greater than 1");
_setParameter(_marketKey, PARAMETER_MAKER_FEE_NEXT_PRICE, _makerFeeNextPrice);
}
function setNextPriceConfirmWindow(bytes32 _marketKey, uint _nextPriceConfirmWindow) public onlyOwner {
_setParameter(_marketKey, PARAMETER_NEXT_PRICE_CONFIRM_WINDOW, _nextPriceConfirmWindow);
}
function setMaxLeverage(bytes32 _marketKey, uint _maxLeverage) public onlyOwner {
_setParameter(_marketKey, PARAMETER_MAX_LEVERAGE, _maxLeverage);
}
function setMaxMarketValueUSD(bytes32 _marketKey, uint _maxMarketValueUSD) public onlyOwner {
_setParameter(_marketKey, PARAMETER_MAX_MARKET_VALUE, _maxMarketValueUSD);
}
// Before altering parameters relevant to funding rates, outstanding funding on the underlying market
// must be recomputed, otherwise already-accrued but unrealised funding in the market can change.
function _recomputeFunding(bytes32 _marketKey) internal {
IFuturesMarket market = IFuturesMarket(_futuresMarketManager().marketForKey(_marketKey));
if (market.marketSize() > 0) {
// only recompute funding when market has positions, this check is important for initial setup
market.recomputeFunding();
}
}
function setMaxFundingRate(bytes32 _marketKey, uint _maxFundingRate) public onlyOwner {
_recomputeFunding(_marketKey);
_setParameter(_marketKey, PARAMETER_MAX_FUNDING_RATE, _maxFundingRate);
}
function setSkewScaleUSD(bytes32 _marketKey, uint _skewScaleUSD) public onlyOwner {
require(_skewScaleUSD > 0, "cannot set skew scale 0");
_recomputeFunding(_marketKey);
_setParameter(_marketKey, PARAMETER_MIN_SKEW_SCALE, _skewScaleUSD);
}
function setParameters(
bytes32 _marketKey,
uint _takerFee,
uint _makerFee,
uint _takerFeeNextPrice,
uint _makerFeeNextPrice,
uint _nextPriceConfirmWindow,
uint _maxLeverage,
uint _maxMarketValueUSD,
uint _maxFundingRate,
uint _skewScaleUSD
) external onlyOwner {
_recomputeFunding(_marketKey);
setTakerFee(_marketKey, _takerFee);
setMakerFee(_marketKey, _makerFee);
setTakerFeeNextPrice(_marketKey, _takerFeeNextPrice);
setMakerFeeNextPrice(_marketKey, _makerFeeNextPrice);
setNextPriceConfirmWindow(_marketKey, _nextPriceConfirmWindow);
setMaxLeverage(_marketKey, _maxLeverage);
setMaxMarketValueUSD(_marketKey, _maxMarketValueUSD);
setMaxFundingRate(_marketKey, _maxFundingRate);
setSkewScaleUSD(_marketKey, _skewScaleUSD);
}
function setMinKeeperFee(uint _sUSD) external onlyOwner {
require(_sUSD <= _minInitialMargin(), "min margin < liquidation fee");
_flexibleStorage().setUIntValue(SETTING_CONTRACT_NAME, SETTING_MIN_KEEPER_FEE, _sUSD);
emit MinKeeperFeeUpdated(_sUSD);
}
function setLiquidationFeeRatio(uint _ratio) external onlyOwner {
_flexibleStorage().setUIntValue(SETTING_CONTRACT_NAME, SETTING_LIQUIDATION_FEE_RATIO, _ratio);
emit LiquidationFeeRatioUpdated(_ratio);
}
function setLiquidationBufferRatio(uint _ratio) external onlyOwner {
_flexibleStorage().setUIntValue(SETTING_CONTRACT_NAME, SETTING_LIQUIDATION_BUFFER_RATIO, _ratio);
emit LiquidationBufferRatioUpdated(_ratio);
}
function setMinInitialMargin(uint _minMargin) external onlyOwner {
require(_minKeeperFee() <= _minMargin, "min margin < liquidation fee");
_flexibleStorage().setUIntValue(SETTING_CONTRACT_NAME, SETTING_MIN_INITIAL_MARGIN, _minMargin);
emit MinInitialMarginUpdated(_minMargin);
}
/* ========== EVENTS ========== */
event ParameterUpdated(bytes32 indexed marketKey, bytes32 indexed parameter, uint value);
event MinKeeperFeeUpdated(uint sUSD);
event LiquidationFeeRatioUpdated(uint bps);
event LiquidationBufferRatioUpdated(uint bps);
event MinInitialMarginUpdated(uint minMargin);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
// Source adapted from https://github.com/EthWorks/Doppelganger/blob/master/contracts/Doppelganger.sol
pragma solidity ^0.5.16;
contract GenericMock {
mapping(bytes4 => bytes) public mockConfig;
// solhint-disable payable-fallback, no-complex-fallback
function() external {
bytes memory ret = mockConfig[msg.sig];
assembly {
return(add(ret, 0x20), mload(ret))
}
}
function mockReturns(bytes4 key, bytes calldata value) external {
mockConfig[key] = value;
}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
// https://docs.synthetix.io/contracts/source/interfaces/iaddressresolver
interface IAddressResolver {
function getAddress(bytes32 name) external view returns (address);
function getSynth(bytes32 key) external view returns (address);
function requireAndGetAddress(bytes32 name, string calldata reason) external view returns (address);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
pragma experimental ABIEncoderV2;
interface IBaseSynthetixBridge {
function suspendInitiation() external;
function resumeInitiation() external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
// https://docs.synthetix.io/contracts/source/interfaces/ICircuitBreaker
interface ICircuitBreaker {
// Views
function isInvalid(address oracleAddress, uint value) external view returns (bool);
function priceDeviationThresholdFactor() external view returns (uint);
function isDeviationAboveThreshold(uint base, uint comparison) external view returns (bool);
function lastValue(address oracleAddress) external view returns (uint);
function circuitBroken(address oracleAddress) external view returns (bool);
// Mutative functions
function resetLastValue(address[] calldata oracleAddresses, uint[] calldata values) external;
function probeCircuitBreaker(address oracleAddress, uint value) external returns (bool circuitBroken);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
interface ICollateralErc20 {
function open(
uint collateral,
uint amount,
bytes32 currency
) external returns (uint id);
function close(uint id) external returns (uint amount, uint collateral);
function deposit(
address borrower,
uint id,
uint amount
) external returns (uint principal, uint collateral);
function withdraw(uint id, uint amount) external returns (uint principal, uint collateral);
function repay(
address borrower,
uint id,
uint amount
) external returns (uint principal, uint collateral);
function draw(uint id, uint amount) external returns (uint principal, uint collateral);
function liquidate(
address borrower,
uint id,
uint amount
) external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
interface ICollateralEth {
function open(uint amount, bytes32 currency) external payable returns (uint id);
function close(uint id) external returns (uint amount, uint collateral);
function deposit(address borrower, uint id) external payable returns (uint principal, uint collateral);
function withdraw(uint id, uint amount) external returns (uint principal, uint collateral);
function repay(
address borrower,
uint id,
uint amount
) external returns (uint principal, uint collateral);
function draw(uint id, uint amount) external returns (uint principal, uint collateral);
function liquidate(
address borrower,
uint id,
uint amount
) external;
function claim(uint amount) external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
pragma experimental ABIEncoderV2;
interface ICollateralLoan {
struct Loan {
// ID for the loan
uint id;
// Acccount that created the loan
address payable account;
// Amount of collateral deposited
uint collateral;
// The synth that was borowed
bytes32 currency;
// Amount of synths borrowed
uint amount;
// Indicates if the position was short sold
bool short;
// interest amounts accrued
uint accruedInterest;
// last interest index
uint interestIndex;
// time of last interaction.
uint lastInteraction;
}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
interface ICollateralManager {
// Manager information
function hasCollateral(address collateral) external view returns (bool);
function isSynthManaged(bytes32 currencyKey) external view returns (bool);
// State information
function long(bytes32 synth) external view returns (uint amount);
function short(bytes32 synth) external view returns (uint amount);
function totalLong() external view returns (uint susdValue, bool anyRateIsInvalid);
function totalShort() external view returns (uint susdValue, bool anyRateIsInvalid);
function getBorrowRate() external view returns (uint borrowRate, bool anyRateIsInvalid);
function getShortRate(bytes32 synth) external view returns (uint shortRate, bool rateIsInvalid);
function getRatesAndTime(uint index)
external
view
returns (
uint entryRate,
uint lastRate,
uint lastUpdated,
uint newIndex
);
function getShortRatesAndTime(bytes32 currency, uint index)
external
view
returns (
uint entryRate,
uint lastRate,
uint lastUpdated,
uint newIndex
);
function exceedsDebtLimit(uint amount, bytes32 currency) external view returns (bool canIssue, bool anyRateIsInvalid);
function areSynthsAndCurrenciesSet(bytes32[] calldata requiredSynthNamesInResolver, bytes32[] calldata synthKeys)
external
view
returns (bool);
function areShortableSynthsSet(bytes32[] calldata requiredSynthNamesInResolver, bytes32[] calldata synthKeys)
external
view
returns (bool);
// Loans
function getNewLoanId() external returns (uint id);
// Manager mutative
function addCollaterals(address[] calldata collaterals) external;
function removeCollaterals(address[] calldata collaterals) external;
function addSynths(bytes32[] calldata synthNamesInResolver, bytes32[] calldata synthKeys) external;
function removeSynths(bytes32[] calldata synths, bytes32[] calldata synthKeys) external;
function addShortableSynths(bytes32[] calldata requiredSynthNamesInResolver, bytes32[] calldata synthKeys) external;
function removeShortableSynths(bytes32[] calldata synths) external;
// State mutative
function incrementLongs(bytes32 synth, uint amount) external;
function decrementLongs(bytes32 synth, uint amount) external;
function incrementShorts(bytes32 synth, uint amount) external;
function decrementShorts(bytes32 synth, uint amount) external;
function accrueInterest(
uint interestIndex,
bytes32 currency,
bool isShort
) external returns (uint difference, uint index);
function updateBorrowRatesCollateral(uint rate) external;
function updateShortRatesCollateral(bytes32 currency, uint rate) external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
pragma experimental ABIEncoderV2;
import "./ICollateralLoan.sol";
interface ICollateralUtil {
function getCollateralRatio(ICollateralLoan.Loan calldata loan, bytes32 collateralKey)
external
view
returns (uint cratio);
function maxLoan(
uint amount,
bytes32 currency,
uint minCratio,
bytes32 collateralKey
) external view returns (uint max);
function liquidationAmount(
ICollateralLoan.Loan calldata loan,
uint minCratio,
bytes32 collateralKey
) external view returns (uint amount);
function collateralRedeemed(
bytes32 currency,
uint amount,
bytes32 collateralKey
) external view returns (uint collateral);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
import "./IIssuer.sol";
interface IDebtCache {
// Views
function cachedDebt() external view returns (uint);
function cachedSynthDebt(bytes32 currencyKey) external view returns (uint);
function cacheTimestamp() external view returns (uint);
function cacheInvalid() external view returns (bool);
function cacheStale() external view returns (bool);
function isInitialized() external view returns (bool);
function currentSynthDebts(bytes32[] calldata currencyKeys)
external
view
returns (
uint[] memory debtValues,
uint futuresDebt,
uint excludedDebt,
bool anyRateIsInvalid
);
function cachedSynthDebts(bytes32[] calldata currencyKeys) external view returns (uint[] memory debtValues);
function totalNonSnxBackedDebt() external view returns (uint excludedDebt, bool isInvalid);
function currentDebt() external view returns (uint debt, bool anyRateIsInvalid);
function cacheInfo()
external
view
returns (
uint debt,
uint timestamp,
bool isInvalid,
bool isStale
);
function excludedIssuedDebts(bytes32[] calldata currencyKeys) external view returns (uint[] memory excludedDebts);
// Mutative functions
function updateCachedSynthDebts(bytes32[] calldata currencyKeys) external;
function updateCachedSynthDebtWithRate(bytes32 currencyKey, uint currencyRate) external;
function updateCachedSynthDebtsWithRates(bytes32[] calldata currencyKeys, uint[] calldata currencyRates) external;
function updateDebtCacheValidity(bool currentlyInvalid) external;
function purgeCachedSynthDebt(bytes32 currencyKey) external;
function takeDebtSnapshot() external;
function recordExcludedDebtChange(bytes32 currencyKey, int256 delta) external;
function updateCachedsUSDDebt(int amount) external;
function importExcludedIssuedDebts(IDebtCache prevDebtCache, IIssuer prevIssuer) external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
pragma experimental ABIEncoderV2;
interface IDebtMigrator {
function finalizeDebtMigration(
address account,
uint debtSharesMigrated,
uint escrowMigrated,
uint liquidSnxMigrated,
bytes calldata debtPayload
) external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
// https://docs.synthetix.io/contracts/source/interfaces/idelegateapprovals
interface IDelegateApprovals {
// Views
function canBurnFor(address authoriser, address delegate) external view returns (bool);
function canIssueFor(address authoriser, address delegate) external view returns (bool);
function canClaimFor(address authoriser, address delegate) external view returns (bool);
function canExchangeFor(address authoriser, address delegate) external view returns (bool);
// Mutative
function approveAllDelegatePowers(address delegate) external;
function removeAllDelegatePowers(address delegate) external;
function approveBurnOnBehalf(address delegate) external;
function removeBurnOnBehalf(address delegate) external;
function approveIssueOnBehalf(address delegate) external;
function removeIssueOnBehalf(address delegate) external;
function approveClaimOnBehalf(address delegate) external;
function removeClaimOnBehalf(address delegate) external;
function approveExchangeOnBehalf(address delegate) external;
function removeExchangeOnBehalf(address delegate) external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
// https://docs.synthetix.io/contracts/source/interfaces/idepot
interface IDepot {
// Views
function fundsWallet() external view returns (address payable);
function maxEthPurchase() external view returns (uint);
function minimumDepositAmount() external view returns (uint);
function synthsReceivedForEther(uint amount) external view returns (uint);
function totalSellableDeposits() external view returns (uint);
// Mutative functions
function depositSynths(uint amount) external;
function exchangeEtherForSynths() external payable returns (uint);
function exchangeEtherForSynthsAtRate(uint guaranteedRate) external payable returns (uint);
function withdrawMyDepositedSynths() external;
// Note: On mainnet no SNX has been deposited. The following functions are kept alive for testnet SNX faucets.
function exchangeEtherForSNX() external payable returns (uint);
function exchangeEtherForSNXAtRate(uint guaranteedRate, uint guaranteedSynthetixRate) external payable returns (uint);
function exchangeSynthsForSNX(uint synthAmount) external returns (uint);
function synthetixReceivedForEther(uint amount) external view returns (uint);
function synthetixReceivedForSynths(uint amount) external view returns (uint);
function withdrawSynthetix(uint amount) external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
// https://sips.synthetix.io/sips/sip-120/
// Uniswap V3 based DecPriceAggregator (unaudited) e.g. https://etherscan.io/address/0xf120f029ac143633d1942e48ae2dfa2036c5786c#code
// https://github.com/sohkai/uniswap-v3-spot-twap-oracle
// inteface: https://github.com/sohkai/uniswap-v3-spot-twap-oracle/blob/8f9777a6160a089c99f39f2ee297119ee293bc4b/contracts/interfaces/IDexPriceAggregator.sol
// implementation: https://github.com/sohkai/uniswap-v3-spot-twap-oracle/blob/8f9777a6160a089c99f39f2ee297119ee293bc4b/contracts/DexPriceAggregatorUniswapV3.sol
interface IDexPriceAggregator {
function assetToAsset(
address tokenIn,
uint amountIn,
address tokenOut,
uint twapPeriod
) external view returns (uint amountOut);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
pragma experimental ABIEncoderV2;
// https://docs.synthetix.io/contracts/source/interfaces/IDirectIntegration
interface IDirectIntegrationManager {
struct ParameterIntegrationSettings {
bytes32 currencyKey;
address dexPriceAggregator;
address atomicEquivalentForDexPricing;
uint atomicExchangeFeeRate;
uint atomicTwapWindow;
uint atomicMaxVolumePerBlock;
uint atomicVolatilityConsiderationWindow;
uint atomicVolatilityUpdateThreshold;
uint exchangeFeeRate;
uint exchangeMaxDynamicFee;
uint exchangeDynamicFeeRounds;
uint exchangeDynamicFeeThreshold;
uint exchangeDynamicFeeWeightDecay;
}
function getExchangeParameters(address integration, bytes32 key)
external
view
returns (ParameterIntegrationSettings memory settings);
function setExchangeParameters(
address integration,
bytes32[] calldata currencyKeys,
ParameterIntegrationSettings calldata params
) external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP. Does not include
* the optional functions; to access them see `ERC20Detailed`.
*/
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.
*
* > 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);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
import "./IWETH.sol";
// https://docs.synthetix.io/contracts/source/interfaces/ietherwrapper
contract IEtherWrapper {
function mint(uint amount) external;
function burn(uint amount) external;
function distributeFees() external;
function capacity() external view returns (uint);
function getReserves() external view returns (uint);
function totalIssuedSynths() external view returns (uint);
function calculateMintFee(uint amount) public view returns (uint);
function calculateBurnFee(uint amount) public view returns (uint);
function maxETH() public view returns (uint256);
function mintFeeRate() public view returns (uint256);
function burnFeeRate() public view returns (uint256);
function weth() public view returns (IWETH);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
import "./IExchangeRates.sol";
// https://docs.synthetix.io/contracts/source/interfaces/IExchangeCircuitBreaker
interface IExchangeCircuitBreaker {
// Views
function exchangeRates() external view returns (IExchangeRates);
function rateWithInvalid(bytes32 currencyKey) external view returns (uint, bool);
function rateWithBreakCircuit(bytes32 currencyKey) external returns (uint lastValidRate, bool circuitBroken);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
pragma experimental ABIEncoderV2;
import "./IVirtualSynth.sol";
// https://docs.synthetix.io/contracts/source/interfaces/iexchanger
interface IExchanger {
struct ExchangeEntrySettlement {
bytes32 src;
uint amount;
bytes32 dest;
uint reclaim;
uint rebate;
uint srcRoundIdAtPeriodEnd;
uint destRoundIdAtPeriodEnd;
uint timestamp;
}
struct ExchangeEntry {
uint sourceRate;
uint destinationRate;
uint destinationAmount;
uint exchangeFeeRate;
uint exchangeDynamicFeeRate;
uint roundIdForSrc;
uint roundIdForDest;
uint sourceAmountAfterSettlement;
}
// Views
function calculateAmountAfterSettlement(
address from,
bytes32 currencyKey,
uint amount,
uint refunded
) external view returns (uint amountAfterSettlement);
function isSynthRateInvalid(bytes32 currencyKey) external view returns (bool);
function maxSecsLeftInWaitingPeriod(address account, bytes32 currencyKey) external view returns (uint);
function settlementOwing(address account, bytes32 currencyKey)
external
view
returns (
uint reclaimAmount,
uint rebateAmount,
uint numEntries
);
function hasWaitingPeriodOrSettlementOwing(address account, bytes32 currencyKey) external view returns (bool);
function feeRateForExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey) external view returns (uint);
function dynamicFeeRateForExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey)
external
view
returns (uint feeRate, bool tooVolatile);
function getAmountsForExchange(
uint sourceAmount,
bytes32 sourceCurrencyKey,
bytes32 destinationCurrencyKey
)
external
view
returns (
uint amountReceived,
uint fee,
uint exchangeFeeRate
);
function priceDeviationThresholdFactor() external view returns (uint);
function waitingPeriodSecs() external view returns (uint);
function lastExchangeRate(bytes32 currencyKey) external view returns (uint);
// Mutative functions
function exchange(
address exchangeForAddress,
address from,
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey,
address destinationAddress,
bool virtualSynth,
address rewardAddress,
bytes32 trackingCode
) external returns (uint amountReceived, IVirtualSynth vSynth);
function exchangeAtomically(
address from,
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey,
address destinationAddress,
bytes32 trackingCode,
uint minAmount
) external returns (uint amountReceived);
function settle(address from, bytes32 currencyKey)
external
returns (
uint reclaimed,
uint refunded,
uint numEntries
);
}
// Used to have strongly-typed access to internal mutative functions in Synthetix
interface ISynthetixInternal {
function emitExchangeTracking(
bytes32 trackingCode,
bytes32 toCurrencyKey,
uint256 toAmount,
uint256 fee
) external;
function emitSynthExchange(
address account,
bytes32 fromCurrencyKey,
uint fromAmount,
bytes32 toCurrencyKey,
uint toAmount,
address toAddress
) external;
function emitAtomicSynthExchange(
address account,
bytes32 fromCurrencyKey,
uint fromAmount,
bytes32 toCurrencyKey,
uint toAmount,
address toAddress
) external;
function emitExchangeReclaim(
address account,
bytes32 currencyKey,
uint amount
) external;
function emitExchangeRebate(
address account,
bytes32 currencyKey,
uint amount
) external;
}
interface IExchangerInternalDebtCache {
function updateCachedSynthDebtsWithRates(bytes32[] calldata currencyKeys, uint[] calldata currencyRates) external;
function updateCachedSynthDebts(bytes32[] calldata currencyKeys) external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
pragma experimental ABIEncoderV2;
import "./IDirectIntegrationManager.sol";
// https://docs.synthetix.io/contracts/source/interfaces/iexchangerates
interface IExchangeRates {
// Structs
struct RateAndUpdatedTime {
uint216 rate;
uint40 time;
}
// Views
function aggregators(bytes32 currencyKey) external view returns (address);
function aggregatorWarningFlags() external view returns (address);
function anyRateIsInvalid(bytes32[] calldata currencyKeys) external view returns (bool);
function anyRateIsInvalidAtRound(bytes32[] calldata currencyKeys, uint[] calldata roundIds) external view returns (bool);
function currenciesUsingAggregator(address aggregator) external view returns (bytes32[] memory);
function effectiveValue(
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey
) external view returns (uint value);
function effectiveValueAndRates(
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey
)
external
view
returns (
uint value,
uint sourceRate,
uint destinationRate
);
function effectiveValueAndRatesAtRound(
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey,
uint roundIdForSrc,
uint roundIdForDest
)
external
view
returns (
uint value,
uint sourceRate,
uint destinationRate
);
function effectiveAtomicValueAndRates(
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey
)
external
view
returns (
uint value,
uint systemValue,
uint systemSourceRate,
uint systemDestinationRate
);
function effectiveAtomicValueAndRates(
IDirectIntegrationManager.ParameterIntegrationSettings calldata sourceSettings,
uint sourceAmount,
IDirectIntegrationManager.ParameterIntegrationSettings calldata destinationSettings,
IDirectIntegrationManager.ParameterIntegrationSettings calldata usdSettings
)
external
view
returns (
uint value,
uint systemValue,
uint systemSourceRate,
uint systemDestinationRate
);
function getCurrentRoundId(bytes32 currencyKey) external view returns (uint);
function getLastRoundIdBeforeElapsedSecs(
bytes32 currencyKey,
uint startingRoundId,
uint startingTimestamp,
uint timediff
) external view returns (uint);
function lastRateUpdateTimes(bytes32 currencyKey) external view returns (uint256);
function rateAndTimestampAtRound(bytes32 currencyKey, uint roundId) external view returns (uint rate, uint time);
function rateAndUpdatedTime(bytes32 currencyKey) external view returns (uint rate, uint time);
function rateAndInvalid(bytes32 currencyKey) external view returns (uint rate, bool isInvalid);
function rateForCurrency(bytes32 currencyKey) external view returns (uint);
function rateIsFlagged(bytes32 currencyKey) external view returns (bool);
function rateIsInvalid(bytes32 currencyKey) external view returns (bool);
function rateIsStale(bytes32 currencyKey) external view returns (bool);
function rateStalePeriod() external view returns (uint);
function ratesAndUpdatedTimeForCurrencyLastNRounds(
bytes32 currencyKey,
uint numRounds,
uint roundId
) external view returns (uint[] memory rates, uint[] memory times);
function ratesAndInvalidForCurrencies(bytes32[] calldata currencyKeys)
external
view
returns (uint[] memory rates, bool anyRateInvalid);
function ratesForCurrencies(bytes32[] calldata currencyKeys) external view returns (uint[] memory);
function synthTooVolatileForAtomicExchange(bytes32 currencyKey) external view returns (bool);
function synthTooVolatileForAtomicExchange(IDirectIntegrationManager.ParameterIntegrationSettings calldata settings)
external
view
returns (bool);
function rateWithSafetyChecks(bytes32 currencyKey)
external
returns (
uint rate,
bool broken,
bool invalid
);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
// https://docs.synthetix.io/contracts/source/interfaces/iexchangestate
interface IExchangeState {
// Views
struct ExchangeEntry {
bytes32 src;
uint amount;
bytes32 dest;
uint amountReceived;
uint exchangeFeeRate;
uint timestamp;
uint roundIdForSrc;
uint roundIdForDest;
}
function getLengthOfEntries(address account, bytes32 currencyKey) external view returns (uint);
function getEntryAt(
address account,
bytes32 currencyKey,
uint index
)
external
view
returns (
bytes32 src,
uint amount,
bytes32 dest,
uint amountReceived,
uint exchangeFeeRate,
uint timestamp,
uint roundIdForSrc,
uint roundIdForDest
);
function getMaxTimestamp(address account, bytes32 currencyKey) external view returns (uint);
// Mutative functions
function appendExchangeEntry(
address account,
bytes32 src,
uint amount,
bytes32 dest,
uint amountReceived,
uint exchangeFeeRate,
uint timestamp,
uint roundIdForSrc,
uint roundIdForDest
) external;
function removeEntries(address account, bytes32 currencyKey) external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
// https://docs.synthetix.io/contracts/source/interfaces/ifeepool
interface IFeePool {
// Views
// solhint-disable-next-line func-name-mixedcase
function FEE_ADDRESS() external view returns (address);
function feesAvailable(address account) external view returns (uint, uint);
function feesBurned(address account) external view returns (uint);
function feesToBurn(address account) external view returns (uint);
function feePeriodDuration() external view returns (uint);
function isFeesClaimable(address account) external view returns (bool);
function targetThreshold() external view returns (uint);
function totalFeesAvailable() external view returns (uint);
function totalFeesBurned() external view returns (uint);
function totalRewardsAvailable() external view returns (uint);
// Mutative Functions
function claimFees() external returns (bool);
function claimOnBehalf(address claimingForAddress) external returns (bool);
function closeCurrentFeePeriod() external;
function closeSecondary(uint snxBackedDebt, uint debtShareSupply) external;
function recordFeePaid(uint sUSDAmount) external;
function setRewardsToDistribute(uint amount) external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
// https://docs.synthetix.io/contracts/source/interfaces/iflexiblestorage
interface IFlexibleStorage {
// Views
function getUIntValue(bytes32 contractName, bytes32 record) external view returns (uint);
function getUIntValues(bytes32 contractName, bytes32[] calldata records) external view returns (uint[] memory);
function getIntValue(bytes32 contractName, bytes32 record) external view returns (int);
function getIntValues(bytes32 contractName, bytes32[] calldata records) external view returns (int[] memory);
function getAddressValue(bytes32 contractName, bytes32 record) external view returns (address);
function getAddressValues(bytes32 contractName, bytes32[] calldata records) external view returns (address[] memory);
function getBoolValue(bytes32 contractName, bytes32 record) external view returns (bool);
function getBoolValues(bytes32 contractName, bytes32[] calldata records) external view returns (bool[] memory);
function getBytes32Value(bytes32 contractName, bytes32 record) external view returns (bytes32);
function getBytes32Values(bytes32 contractName, bytes32[] calldata records) external view returns (bytes32[] memory);
// Mutative functions
function deleteUIntValue(bytes32 contractName, bytes32 record) external;
function deleteIntValue(bytes32 contractName, bytes32 record) external;
function deleteAddressValue(bytes32 contractName, bytes32 record) external;
function deleteBoolValue(bytes32 contractName, bytes32 record) external;
function deleteBytes32Value(bytes32 contractName, bytes32 record) external;
function setUIntValue(
bytes32 contractName,
bytes32 record,
uint value
) external;
function setUIntValues(
bytes32 contractName,
bytes32[] calldata records,
uint[] calldata values
) external;
function setIntValue(
bytes32 contractName,
bytes32 record,
int value
) external;
function setIntValues(
bytes32 contractName,
bytes32[] calldata records,
int[] calldata values
) external;
function setAddressValue(
bytes32 contractName,
bytes32 record,
address value
) external;
function setAddressValues(
bytes32 contractName,
bytes32[] calldata records,
address[] calldata values
) external;
function setBoolValue(
bytes32 contractName,
bytes32 record,
bool value
) external;
function setBoolValues(
bytes32 contractName,
bytes32[] calldata records,
bool[] calldata values
) external;
function setBytes32Value(
bytes32 contractName,
bytes32 record,
bytes32 value
) external;
function setBytes32Values(
bytes32 contractName,
bytes32[] calldata records,
bytes32[] calldata values
) external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
import "./IFuturesMarketBaseTypes.sol";
interface IFuturesMarket {
/* ========== FUNCTION INTERFACE ========== */
/* ---------- Market Details ---------- */
function marketKey() external view returns (bytes32 key);
function baseAsset() external view returns (bytes32 key);
function marketSize() external view returns (uint128 size);
function marketSkew() external view returns (int128 skew);
function fundingLastRecomputed() external view returns (uint32 timestamp);
function fundingSequence(uint index) external view returns (int128 netFunding);
function positions(address account)
external
view
returns (
uint64 id,
uint64 fundingIndex,
uint128 margin,
uint128 lastPrice,
int128 size
);
function assetPrice() external view returns (uint price, bool invalid);
function marketSizes() external view returns (uint long, uint short);
function marketDebt() external view returns (uint debt, bool isInvalid);
function currentFundingRate() external view returns (int fundingRate);
function unrecordedFunding() external view returns (int funding, bool invalid);
function fundingSequenceLength() external view returns (uint length);
/* ---------- Position Details ---------- */
function notionalValue(address account) external view returns (int value, bool invalid);
function profitLoss(address account) external view returns (int pnl, bool invalid);
function accruedFunding(address account) external view returns (int funding, bool invalid);
function remainingMargin(address account) external view returns (uint marginRemaining, bool invalid);
function accessibleMargin(address account) external view returns (uint marginAccessible, bool invalid);
function liquidationPrice(address account) external view returns (uint price, bool invalid);
function liquidationFee(address account) external view returns (uint);
function canLiquidate(address account) external view returns (bool);
function orderFee(int sizeDelta) external view returns (uint fee, bool invalid);
function postTradeDetails(int sizeDelta, address sender)
external
view
returns (
uint margin,
int size,
uint price,
uint liqPrice,
uint fee,
IFuturesMarketBaseTypes.Status status
);
/* ---------- Market Operations ---------- */
function recomputeFunding() external returns (uint lastIndex);
function transferMargin(int marginDelta) external;
function withdrawAllMargin() external;
function modifyPosition(int sizeDelta) external;
function modifyPositionWithTracking(int sizeDelta, bytes32 trackingCode) external;
function submitNextPriceOrder(int sizeDelta) external;
function submitNextPriceOrderWithTracking(int sizeDelta, bytes32 trackingCode) external;
function cancelNextPriceOrder(address account) external;
function executeNextPriceOrder(address account) external;
function closePosition() external;
function closePositionWithTracking(bytes32 trackingCode) external;
function liquidatePosition(address account) external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
interface IFuturesMarketBaseTypes {
/* ========== TYPES ========== */
enum Status {
Ok,
InvalidPrice,
PriceOutOfBounds,
CanLiquidate,
CannotLiquidate,
MaxMarketSizeExceeded,
MaxLeverageExceeded,
InsufficientMargin,
NotPermitted,
NilOrder,
NoPositionOpen,
PriceTooVolatile
}
// If margin/size are positive, the position is long; if negative then it is short.
struct Position {
uint64 id;
uint64 lastFundingIndex;
uint128 margin;
uint128 lastPrice;
int128 size;
}
// next-price order storage
struct NextPriceOrder {
int128 sizeDelta; // difference in position to pass to modifyPosition
uint128 targetRoundId; // price oracle roundId using which price this order needs to exucted
uint128 commitDeposit; // the commitDeposit paid upon submitting that needs to be refunded if order succeeds
uint128 keeperDeposit; // the keeperDeposit paid upon submitting that needs to be paid / refunded on tx confirmation
bytes32 trackingCode; // tracking code to emit on execution for volume source fee sharing
}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
interface IFuturesMarketManager {
function markets(uint index, uint pageSize) external view returns (address[] memory);
function markets(
uint index,
uint pageSize,
bool proxiedMarkets
) external view returns (address[] memory);
function numMarkets() external view returns (uint);
function numMarkets(bool proxiedMarkets) external view returns (uint);
function allMarkets() external view returns (address[] memory);
function allMarkets(bool proxiedMarkets) external view returns (address[] memory);
function marketForKey(bytes32 marketKey) external view returns (address);
function marketsForKeys(bytes32[] calldata marketKeys) external view returns (address[] memory);
function totalDebt() external view returns (uint debt, bool isInvalid);
function isEndorsed(address account) external view returns (bool);
function allEndorsedAddresses() external view returns (address[] memory);
function addEndorsedAddresses(address[] calldata addresses) external;
function removeEndorsedAddresses(address[] calldata addresses) external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
interface IFuturesMarketSettings {
struct Parameters {
uint takerFee;
uint makerFee;
uint takerFeeNextPrice;
uint makerFeeNextPrice;
uint nextPriceConfirmWindow;
uint maxLeverage;
uint maxMarketValueUSD;
uint maxFundingRate;
uint skewScaleUSD;
}
function takerFee(bytes32 _marketKey) external view returns (uint);
function makerFee(bytes32 _marketKey) external view returns (uint);
function takerFeeNextPrice(bytes32 _marketKey) external view returns (uint);
function makerFeeNextPrice(bytes32 _marketKey) external view returns (uint);
function nextPriceConfirmWindow(bytes32 _marketKey) external view returns (uint);
function maxLeverage(bytes32 _marketKey) external view returns (uint);
function maxMarketValueUSD(bytes32 _marketKey) external view returns (uint);
function maxFundingRate(bytes32 _marketKey) external view returns (uint);
function skewScaleUSD(bytes32 _marketKey) external view returns (uint);
function parameters(bytes32 _marketKey)
external
view
returns (
uint _takerFee,
uint _makerFee,
uint _takerFeeNextPrice,
uint _makerFeeNextPrice,
uint _nextPriceConfirmWindow,
uint _maxLeverage,
uint _maxMarketValueUSD,
uint _maxFundingRate,
uint _skewScaleUSD
);
function minKeeperFee() external view returns (uint);
function liquidationFeeRatio() external view returns (uint);
function liquidationBufferRatio() external view returns (uint);
function minInitialMargin() external view returns (uint);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
// https://docs.synthetix.io/contracts/source/interfaces/ihasbalance
interface IHasBalance {
// Views
function balanceOf(address account) external view returns (uint);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
import "./ISynth.sol";
// https://docs.synthetix.io/contracts/source/interfaces/iissuer
interface IIssuer {
// Views
function allNetworksDebtInfo()
external
view
returns (
uint256 debt,
uint256 sharesSupply,
bool isStale
);
function anySynthOrSNXRateIsInvalid() external view returns (bool anyRateInvalid);
function availableCurrencyKeys() external view returns (bytes32[] memory);
function availableSynthCount() external view returns (uint);
function availableSynths(uint index) external view returns (ISynth);
function canBurnSynths(address account) external view returns (bool);
function collateral(address account) external view returns (uint);
function collateralisationRatio(address issuer) external view returns (uint);
function collateralisationRatioAndAnyRatesInvalid(address _issuer)
external
view
returns (uint cratio, bool anyRateIsInvalid);
function debtBalanceOf(address issuer, bytes32 currencyKey) external view returns (uint debtBalance);
function issuanceRatio() external view returns (uint);
function lastIssueEvent(address account) external view returns (uint);
function maxIssuableSynths(address issuer) external view returns (uint maxIssuable);
function minimumStakeTime() external view returns (uint);
function remainingIssuableSynths(address issuer)
external
view
returns (
uint maxIssuable,
uint alreadyIssued,
uint totalSystemDebt
);
function synths(bytes32 currencyKey) external view returns (ISynth);
function getSynths(bytes32[] calldata currencyKeys) external view returns (ISynth[] memory);
function synthsByAddress(address synthAddress) external view returns (bytes32);
function totalIssuedSynths(bytes32 currencyKey, bool excludeOtherCollateral) external view returns (uint);
function transferableSynthetixAndAnyRateIsInvalid(address account, uint balance)
external
view
returns (uint transferable, bool anyRateIsInvalid);
function liquidationAmounts(address account, bool isSelfLiquidation)
external
view
returns (
uint totalRedeemed,
uint debtToRemove,
uint escrowToLiquidate,
uint initialDebtBalance
);
// Restricted: used internally to Synthetix
function addSynths(ISynth[] calldata synthsToAdd) external;
function issueSynths(address from, uint amount) external;
function issueSynthsOnBehalf(
address issueFor,
address from,
uint amount
) external;
function issueMaxSynths(address from) external;
function issueMaxSynthsOnBehalf(address issueFor, address from) external;
function burnSynths(address from, uint amount) external;
function burnSynthsOnBehalf(
address burnForAddress,
address from,
uint amount
) external;
function burnSynthsToTarget(address from) external;
function burnSynthsToTargetOnBehalf(address burnForAddress, address from) external;
function burnForRedemption(
address deprecatedSynthProxy,
address account,
uint balance
) external;
function setCurrentPeriodId(uint128 periodId) external;
function liquidateAccount(address account, bool isSelfLiquidation)
external
returns (
uint totalRedeemed,
uint debtRemoved,
uint escrowToLiquidate
);
function issueSynthsWithoutDebt(
bytes32 currencyKey,
address to,
uint amount
) external returns (bool rateInvalid);
function burnSynthsWithoutDebt(
bytes32 currencyKey,
address to,
uint amount
) external returns (bool rateInvalid);
function modifyDebtSharesForMigration(address account, uint amount) external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
interface ILiquidator {
// Views
function issuanceRatio() external view returns (uint);
function liquidationDelay() external view returns (uint);
function liquidationRatio() external view returns (uint);
function liquidationEscrowDuration() external view returns (uint);
function liquidationPenalty() external view returns (uint);
function selfLiquidationPenalty() external view returns (uint);
function liquidateReward() external view returns (uint);
function flagReward() external view returns (uint);
function liquidationCollateralRatio() external view returns (uint);
function getLiquidationDeadlineForAccount(address account) external view returns (uint);
function getLiquidationCallerForAccount(address account) external view returns (address);
function isLiquidationOpen(address account, bool isSelfLiquidation) external view returns (bool);
function isLiquidationDeadlinePassed(address account) external view returns (bool);
function calculateAmountToFixCollateral(
uint debtBalance,
uint collateral,
uint penalty
) external view returns (uint);
function liquidationAmounts(address account, bool isSelfLiquidation)
external
view
returns (
uint totalRedeemed,
uint debtToRemove,
uint escrowToLiquidate,
uint initialDebtBalance
);
// Mutative Functions
function flagAccountForLiquidation(address account) external;
// Restricted: used internally to Synthetix contracts
function removeAccountInLiquidation(address account) external;
function checkAndRemoveAccountInLiquidation(address account) external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
interface ILiquidatorRewards {
// Views
function earned(address account) external view returns (uint256);
// Mutative
function getReward(address account) external;
function notifyRewardAmount(uint256 reward) external;
function updateEntry(address account) external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
// solhint-disable payable-fallback
/// a variant of ReadProxy.sol that's not Owned, and so has an immutable target
/// that can be used e.g. to read a oracle feed, and provide its interface, but from a
/// different address
contract ImmutableReadProxy {
address public target;
constructor(address _target) public {
target = _target;
}
function() external {
// The basics of a proxy read call
// Note that msg.sender in the underlying will always be the address of this contract.
assembly {
calldatacopy(0, 0, calldatasize)
// Use of staticcall - this will revert if the underlying function mutates state
let result := staticcall(gas, sload(target_slot), 0, calldatasize, 0, 0)
returndatacopy(0, 0, returndatasize)
if iszero(result) {
revert(0, returndatasize)
}
return(0, returndatasize)
}
}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
pragma experimental ABIEncoderV2;
// Inheritance
import "./BaseRewardEscrowV2.sol";
// https://docs.synthetix.io/contracts/RewardEscrow
contract ImportableRewardEscrowV2 is BaseRewardEscrowV2 {
/* ========== ADDRESS RESOLVER CONFIGURATION ========== */
bytes32 private constant CONTRACT_SYNTHETIX_BRIDGE_BASE = "SynthetixBridgeToBase";
/* ========== CONSTRUCTOR ========== */
constructor(address _owner, address _resolver) public BaseRewardEscrowV2(_owner, _resolver) {}
/* ========== VIEWS ======================= */
function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
bytes32[] memory existingAddresses = BaseRewardEscrowV2.resolverAddressesRequired();
bytes32[] memory newAddresses = new bytes32[](1);
newAddresses[0] = CONTRACT_SYNTHETIX_BRIDGE_BASE;
return combineArrays(existingAddresses, newAddresses);
}
function synthetixBridgeToBase() internal view returns (address) {
return requireAndGetAddress(CONTRACT_SYNTHETIX_BRIDGE_BASE);
}
/* ========== MUTATIVE FUNCTIONS ========== */
function importVestingEntries(
address account,
uint256 escrowedAmount,
VestingEntries.VestingEntry[] calldata vestingEntries
) external onlySynthetixBridge {
// add escrowedAmount to account and total aggregates
state().updateEscrowAccountBalance(account, SafeCast.toInt256(escrowedAmount));
// There must be enough balance in the contract to provide for the escrowed balance.
require(
totalEscrowedBalance() <= synthetixERC20().balanceOf(address(this)),
"Insufficient balance in the contract to provide for escrowed balance"
);
for (uint i = 0; i < vestingEntries.length; i++) {
state().addVestingEntry(account, vestingEntries[i]);
}
}
modifier onlySynthetixBridge() {
require(msg.sender == synthetixBridgeToBase(), "Can only be invoked by SynthetixBridgeToBase contract");
_;
}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
pragma experimental ABIEncoderV2;
// Inheritance
import "./BaseRewardEscrowV2Frozen.sol";
// https://docs.synthetix.io/contracts/RewardEscrow
/// SIP-252: this is the source for immutable V2 escrow on L2 (renamed with suffix Frozen).
/// These sources need to exist here and match on-chain frozen contracts for tests and reference.
/// The reason for the naming mess is that the immutable LiquidatorRewards expects a working
/// RewardEscrowV2 resolver entry for its getReward method, so the "new" (would be V3)
/// needs to be found at that entry for liq-rewards to function.
contract ImportableRewardEscrowV2Frozen is BaseRewardEscrowV2Frozen {
/* ========== ADDRESS RESOLVER CONFIGURATION ========== */
bytes32 private constant CONTRACT_SYNTHETIX_BRIDGE_BASE = "SynthetixBridgeToBase";
/* ========== CONSTRUCTOR ========== */
constructor(address _owner, address _resolver) public BaseRewardEscrowV2Frozen(_owner, _resolver) {}
/* ========== VIEWS ======================= */
function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
bytes32[] memory existingAddresses = BaseRewardEscrowV2Frozen.resolverAddressesRequired();
bytes32[] memory newAddresses = new bytes32[](1);
newAddresses[0] = CONTRACT_SYNTHETIX_BRIDGE_BASE;
return combineArrays(existingAddresses, newAddresses);
}
function synthetixBridgeToBase() internal view returns (address) {
return requireAndGetAddress(CONTRACT_SYNTHETIX_BRIDGE_BASE);
}
/* ========== MUTATIVE FUNCTIONS ========== */
function importVestingEntries(
address account,
uint256 escrowedAmount,
VestingEntries.VestingEntry[] calldata vestingEntries
) external onlySynthetixBridge {
// There must be enough balance in the contract to provide for the escrowed balance.
totalEscrowedBalance = totalEscrowedBalance.add(escrowedAmount);
require(
totalEscrowedBalance <= IERC20(address(synthetix())).balanceOf(address(this)),
"Insufficient balance in the contract to provide for escrowed balance"
);
/* Add escrowedAmount to account's escrowed balance */
totalEscrowedAccountBalance[account] = totalEscrowedAccountBalance[account].add(escrowedAmount);
for (uint i = 0; i < vestingEntries.length; i++) {
_importVestingEntry(account, vestingEntries[i]);
}
}
function _importVestingEntry(address account, VestingEntries.VestingEntry memory entry) internal {
uint entryID = nextEntryId;
vestingSchedules[account][entryID] = entry;
/* append entryID to list of entries for account */
accountVestingEntryIDs[account].push(entryID);
/* Increment the next entry id. */
nextEntryId = nextEntryId.add(1);
}
modifier onlySynthetixBridge() {
require(msg.sender == synthetixBridgeToBase(), "Can only be invoked by SynthetixBridgeToBase contract");
_;
}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
pragma experimental ABIEncoderV2;
interface IOwnerRelayOnOptimism {
function finalizeRelay(address target, bytes calldata payload) external;
function finalizeRelayBatch(address[] calldata target, bytes[] calldata payloads) external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
pragma experimental ABIEncoderV2;
import "./IPyth.sol";
// https://docs.synthetix.io/contracts/source/contracts/IPerpsV2ExchangeRate
interface IPerpsV2ExchangeRate {
function setOffchainOracle(IPyth _offchainOracle) external;
function setOffchainPriceFeedId(bytes32 assetId, bytes32 priceFeedId) external;
/* ========== VIEWS ========== */
function offchainOracle() external view returns (IPyth);
function offchainPriceFeedId(bytes32 assetId) external view returns (bytes32);
/* ---------- priceFeeds mutation ---------- */
function updatePythPrice(address sender, bytes[] calldata priceUpdateData) external payable;
// it is a view but it can revert
function resolveAndGetPrice(bytes32 assetId, uint maxAge) external view returns (uint price, uint publishTime);
// it is a view but it can revert
function resolveAndGetLatestPrice(bytes32 assetId) external view returns (uint price, uint publishTime);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
import "./IPerpsV2MarketBaseTypes.sol";
interface IPerpsV2Market {
/* ========== FUNCTION INTERFACE ========== */
/* ---------- Market Operations ---------- */
function recomputeFunding() external returns (uint lastIndex);
function transferMargin(int marginDelta) external;
function withdrawAllMargin() external;
function modifyPosition(int sizeDelta, uint desiredFillPrice) external;
function modifyPositionWithTracking(
int sizeDelta,
uint desiredFillPrice,
bytes32 trackingCode
) external;
function closePosition(uint desiredFillPrice) external;
function closePositionWithTracking(uint desiredFillPrice, bytes32 trackingCode) external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
interface IPerpsV2MarketBaseTypes {
/* ========== TYPES ========== */
enum OrderType {Atomic, Delayed, Offchain}
enum Status {
Ok,
InvalidPrice,
InvalidOrderType,
PriceOutOfBounds,
CanLiquidate,
CannotLiquidate,
MaxMarketSizeExceeded,
MaxLeverageExceeded,
InsufficientMargin,
NotPermitted,
NilOrder,
NoPositionOpen,
PriceTooVolatile,
PriceImpactToleranceExceeded,
PositionFlagged,
PositionNotFlagged
}
// If margin/size are positive, the position is long; if negative then it is short.
struct Position {
uint64 id;
uint64 lastFundingIndex;
uint128 margin;
uint128 lastPrice;
int128 size;
}
// Delayed order storage
struct DelayedOrder {
bool isOffchain; // flag indicating the delayed order is offchain
int128 sizeDelta; // difference in position to pass to modifyPosition
uint128 desiredFillPrice; // desired fill price as usd used on fillPrice at execution
uint128 targetRoundId; // price oracle roundId using which price this order needs to executed
uint128 commitDeposit; // the commitDeposit paid upon submitting that needs to be refunded if order succeeds
uint128 keeperDeposit; // the keeperDeposit paid upon submitting that needs to be paid / refunded on tx confirmation
uint256 executableAtTime; // The timestamp at which this order is executable at
uint256 intentionTime; // The block timestamp of submission
bytes32 trackingCode; // tracking code to emit on execution for volume source fee sharing
}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
interface IPerpsV2MarketBaseTypesLegacyR1 {
/* ========== TYPES ========== */
enum OrderType {Atomic, Delayed, Offchain}
enum Status {
Ok,
InvalidPrice,
InvalidOrderType,
PriceOutOfBounds,
CanLiquidate,
CannotLiquidate,
MaxMarketSizeExceeded,
MaxLeverageExceeded,
InsufficientMargin,
NotPermitted,
NilOrder,
NoPositionOpen,
PriceTooVolatile,
PriceImpactToleranceExceeded,
PositionFlagged,
PositionNotFlagged
}
// If margin/size are positive, the position is long; if negative then it is short.
struct Position {
uint64 id;
uint64 lastFundingIndex;
uint128 margin;
uint128 lastPrice;
int128 size;
}
// Delayed order storage
struct DelayedOrder {
bool isOffchain; // flag indicating the delayed order is offchain
int128 sizeDelta; // difference in position to pass to modifyPosition
uint128 priceImpactDelta; // desired price delta
uint128 targetRoundId; // price oracle roundId using which price this order needs to executed
uint128 commitDeposit; // the commitDeposit paid upon submitting that needs to be refunded if order succeeds
uint128 keeperDeposit; // the keeperDeposit paid upon submitting that needs to be paid / refunded on tx confirmation
uint256 executableAtTime; // The timestamp at which this order is executable at
uint256 intentionTime; // The block timestamp of submission
bytes32 trackingCode; // tracking code to emit on execution for volume source fee sharing
}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
pragma experimental ABIEncoderV2;
import "./IPerpsV2MarketBaseTypes.sol";
// Helper Interface - used in tests and to provide a consolidated PerpsV2 interface for users/integrators.
interface IPerpsV2MarketConsolidated {
/* ========== TYPES ========== */
enum OrderType {Atomic, Delayed, Offchain}
enum Status {
Ok,
InvalidPrice,
InvalidOrderType,
PriceOutOfBounds,
CanLiquidate,
CannotLiquidate,
MaxMarketSizeExceeded,
MaxLeverageExceeded,
InsufficientMargin,
NotPermitted,
NilOrder,
NoPositionOpen,
PriceTooVolatile,
PriceImpactToleranceExceeded,
PositionFlagged,
PositionNotFlagged
}
/* @dev: See IPerpsV2MarketBaseTypes */
struct Position {
uint64 id;
uint64 lastFundingIndex;
uint128 margin;
uint128 lastPrice;
int128 size;
}
/* @dev: See IPerpsV2MarketBaseTypes */
struct DelayedOrder {
bool isOffchain;
int128 sizeDelta;
uint128 desiredFillPrice;
uint128 targetRoundId;
uint128 commitDeposit;
uint128 keeperDeposit;
uint256 executableAtTime;
uint256 intentionTime;
bytes32 trackingCode;
}
/* ========== Views ========== */
/* ---------- Market Details ---------- */
function marketKey() external view returns (bytes32 key);
function baseAsset() external view returns (bytes32 key);
function marketSize() external view returns (uint128 size);
function marketSkew() external view returns (int128 skew);
function fundingLastRecomputed() external view returns (uint32 timestamp);
function fundingRateLastRecomputed() external view returns (int128 fundingRate);
function fundingSequence(uint index) external view returns (int128 netFunding);
function positions(address account) external view returns (Position memory);
function delayedOrders(address account) external view returns (DelayedOrder memory);
function assetPrice() external view returns (uint price, bool invalid);
function fillPrice(int sizeDelta) external view returns (uint price, bool invalid);
function marketSizes() external view returns (uint long, uint short);
function marketDebt() external view returns (uint debt, bool isInvalid);
function currentFundingRate() external view returns (int fundingRate);
function currentFundingVelocity() external view returns (int fundingVelocity);
function unrecordedFunding() external view returns (int funding, bool invalid);
function fundingSequenceLength() external view returns (uint length);
/* ---------- Position Details ---------- */
function notionalValue(address account) external view returns (int value, bool invalid);
function profitLoss(address account) external view returns (int pnl, bool invalid);
function accruedFunding(address account) external view returns (int funding, bool invalid);
function remainingMargin(address account) external view returns (uint marginRemaining, bool invalid);
function accessibleMargin(address account) external view returns (uint marginAccessible, bool invalid);
function liquidationPrice(address account) external view returns (uint price, bool invalid);
function liquidationFee(address account) external view returns (uint);
function isFlagged(address account) external view returns (bool);
function canLiquidate(address account) external view returns (bool);
function orderFee(int sizeDelta, IPerpsV2MarketBaseTypes.OrderType orderType)
external
view
returns (uint fee, bool invalid);
function postTradeDetails(
int sizeDelta,
uint tradePrice,
IPerpsV2MarketBaseTypes.OrderType orderType,
address sender
)
external
view
returns (
uint margin,
int size,
uint price,
uint liqPrice,
uint fee,
Status status
);
/* ========== Market ========== */
function recomputeFunding() external returns (uint lastIndex);
function transferMargin(int marginDelta) external;
function withdrawAllMargin() external;
function modifyPosition(int sizeDelta, uint desiredFillPrice) external;
function modifyPositionWithTracking(
int sizeDelta,
uint desiredFillPrice,
bytes32 trackingCode
) external;
function closePosition(uint desiredFillPrice) external;
function closePositionWithTracking(uint desiredFillPrice, bytes32 trackingCode) external;
/* ========== Liquidate ========== */
function flagPosition(address account) external;
function liquidatePosition(address account) external;
function forceLiquidatePosition(address account) external;
/* ========== Delayed Intent ========== */
function submitCloseOffchainDelayedOrderWithTracking(uint desiredFillPrice, bytes32 trackingCode) external;
function submitCloseDelayedOrderWithTracking(
uint desiredTimeDelta,
uint desiredFillPrice,
bytes32 trackingCode
) external;
function submitDelayedOrder(
int sizeDelta,
uint desiredTimeDelta,
uint desiredFillPrice
) external;
function submitDelayedOrderWithTracking(
int sizeDelta,
uint desiredTimeDelta,
uint desiredFillPrice,
bytes32 trackingCode
) external;
function submitOffchainDelayedOrder(int sizeDelta, uint desiredFillPrice) external;
function submitOffchainDelayedOrderWithTracking(
int sizeDelta,
uint desiredFillPrice,
bytes32 trackingCode
) external;
/* ========== Delayed Execution ========== */
function executeDelayedOrder(address account) external;
function executeOffchainDelayedOrder(address account, bytes[] calldata priceUpdateData) external payable;
function cancelDelayedOrder(address account) external;
function cancelOffchainDelayedOrder(address account) external;
/* ========== Events ========== */
event PositionModified(
uint indexed id,
address indexed account,
uint margin,
int size,
int tradeSize,
uint lastPrice,
uint fundingIndex,
uint fee,
int skew
);
event MarginTransferred(address indexed account, int marginDelta);
event PositionFlagged(uint id, address account, address flagger, uint price, uint timestamp);
event PositionLiquidated(
uint id,
address account,
address liquidator,
int size,
uint price,
uint flaggerFee,
uint liquidatorFee,
uint stakersFee
);
event FundingRecomputed(int funding, int fundingRate, uint index, uint timestamp);
event PerpsTracking(bytes32 indexed trackingCode, bytes32 baseAsset, bytes32 marketKey, int sizeDelta, uint fee);
event DelayedOrderRemoved(
address indexed account,
bool isOffchain,
uint currentRoundId,
int sizeDelta,
uint targetRoundId,
uint commitDeposit,
uint keeperDeposit,
bytes32 trackingCode
);
event DelayedOrderSubmitted(
address indexed account,
bool isOffchain,
int sizeDelta,
uint targetRoundId,
uint intentionTime,
uint executableAtTime,
uint commitDeposit,
uint keeperDeposit,
bytes32 trackingCode
);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
pragma experimental ABIEncoderV2;
import "./IPerpsV2MarketBaseTypes.sol";
interface IPerpsV2MarketDelayedExecution {
function executeDelayedOrder(address account) external;
function executeOffchainDelayedOrder(address account, bytes[] calldata priceUpdateData) external payable;
function cancelDelayedOrder(address account) external;
function cancelOffchainDelayedOrder(address account) external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
pragma experimental ABIEncoderV2;
import "./IPerpsV2MarketBaseTypes.sol";
interface IPerpsV2MarketDelayedIntent {
function submitCloseOffchainDelayedOrderWithTracking(uint desiredFillPrice, bytes32 trackingCode) external;
function submitCloseDelayedOrderWithTracking(
uint desiredTimeDelta,
uint desiredFillPrice,
bytes32 trackingCode
) external;
function submitDelayedOrder(
int sizeDelta,
uint desiredTimeDelta,
uint desiredFillPrice
) external;
function submitDelayedOrderWithTracking(
int sizeDelta,
uint desiredTimeDelta,
uint desiredFillPrice,
bytes32 trackingCode
) external;
function submitOffchainDelayedOrder(int sizeDelta, uint desiredFillPrice) external;
function submitOffchainDelayedOrderWithTracking(
int sizeDelta,
uint desiredFillPrice,
bytes32 trackingCode
) external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
import "./IPerpsV2MarketBaseTypes.sol";
interface IPerpsV2MarketLiquidate {
/* ========== FUNCTION INTERFACE ========== */
/* ---------- Market Operations ---------- */
function flagPosition(address account) external;
function liquidatePosition(address account) external;
function forceLiquidatePosition(address account) external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
pragma experimental ABIEncoderV2;
interface IPerpsV2MarketSettings {
struct Parameters {
uint takerFee;
uint makerFee;
uint takerFeeDelayedOrder;
uint makerFeeDelayedOrder;
uint takerFeeOffchainDelayedOrder;
uint makerFeeOffchainDelayedOrder;
uint maxLeverage;
uint maxMarketValue;
uint maxFundingVelocity;
uint skewScale;
uint nextPriceConfirmWindow;
uint delayedOrderConfirmWindow;
uint minDelayTimeDelta;
uint maxDelayTimeDelta;
uint offchainDelayedOrderMinAge;
uint offchainDelayedOrderMaxAge;
bytes32 offchainMarketKey;
uint offchainPriceDivergence;
uint liquidationPremiumMultiplier;
uint liquidationBufferRatio;
uint maxLiquidationDelta;
uint maxPD;
}
function takerFee(bytes32 _marketKey) external view returns (uint);
function makerFee(bytes32 _marketKey) external view returns (uint);
function takerFeeDelayedOrder(bytes32 _marketKey) external view returns (uint);
function makerFeeDelayedOrder(bytes32 _marketKey) external view returns (uint);
function takerFeeOffchainDelayedOrder(bytes32 _marketKey) external view returns (uint);
function makerFeeOffchainDelayedOrder(bytes32 _marketKey) external view returns (uint);
function nextPriceConfirmWindow(bytes32 _marketKey) external view returns (uint);
function delayedOrderConfirmWindow(bytes32 _marketKey) external view returns (uint);
function offchainDelayedOrderMinAge(bytes32 _marketKey) external view returns (uint);
function offchainDelayedOrderMaxAge(bytes32 _marketKey) external view returns (uint);
function maxLeverage(bytes32 _marketKey) external view returns (uint);
function maxMarketValue(bytes32 _marketKey) external view returns (uint);
function maxFundingVelocity(bytes32 _marketKey) external view returns (uint);
function skewScale(bytes32 _marketKey) external view returns (uint);
function minDelayTimeDelta(bytes32 _marketKey) external view returns (uint);
function maxDelayTimeDelta(bytes32 _marketKey) external view returns (uint);
function offchainMarketKey(bytes32 _marketKey) external view returns (bytes32);
function offchainPriceDivergence(bytes32 _marketKey) external view returns (uint);
function liquidationPremiumMultiplier(bytes32 _marketKey) external view returns (uint);
function maxPD(bytes32 _marketKey) external view returns (uint);
function maxLiquidationDelta(bytes32 _marketKey) external view returns (uint);
function liquidationBufferRatio(bytes32 _marketKey) external view returns (uint);
function parameters(bytes32 _marketKey) external view returns (Parameters memory);
function minKeeperFee() external view returns (uint);
function maxKeeperFee() external view returns (uint);
function liquidationFeeRatio() external view returns (uint);
function minInitialMargin() external view returns (uint);
function keeperLiquidationFee() external view returns (uint);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
pragma experimental ABIEncoderV2;
import "./IPerpsV2MarketBaseTypes.sol";
// https://docs.synthetix.io/contracts/source/contracts/PerpsV2MarketState
interface IPerpsV2MarketState {
function marketKey() external view returns (bytes32);
function baseAsset() external view returns (bytes32);
function marketSize() external view returns (uint128);
function marketSkew() external view returns (int128);
function fundingLastRecomputed() external view returns (uint32);
function fundingSequence(uint) external view returns (int128);
function fundingRateLastRecomputed() external view returns (int128);
function positions(address) external view returns (IPerpsV2MarketBaseTypes.Position memory);
function delayedOrders(address) external view returns (IPerpsV2MarketBaseTypes.DelayedOrder memory);
function positionFlagger(address) external view returns (address);
function entryDebtCorrection() external view returns (int128);
function nextPositionId() external view returns (uint64);
function fundingSequenceLength() external view returns (uint);
function isFlagged(address) external view returns (bool);
function getPositionAddressesPage(uint, uint) external view returns (address[] memory);
function getPositionAddressesLength() external view returns (uint);
function getDelayedOrderAddressesPage(uint, uint) external view returns (address[] memory);
function getDelayedOrderAddressesLength() external view returns (uint);
function getFlaggedAddressesPage(uint, uint) external view returns (address[] memory);
function getFlaggedAddressesLength() external view returns (uint);
function setMarketKey(bytes32) external;
function setBaseAsset(bytes32) external;
function setMarketSize(uint128) external;
function setEntryDebtCorrection(int128) external;
function setNextPositionId(uint64) external;
function setMarketSkew(int128) external;
function setFundingLastRecomputed(uint32) external;
function setFundingRateLastRecomputed(int128 _fundingRateLastRecomputed) external;
function pushFundingSequence(int128) external;
function updatePosition(
address account,
uint64 id,
uint64 lastFundingIndex,
uint128 margin,
uint128 lastPrice,
int128 size
) external;
function updateDelayedOrder(
address account,
bool isOffchain,
int128 sizeDelta,
uint128 desiredFillPrice,
uint128 targetRoundId,
uint128 commitDeposit,
uint128 keeperDeposit,
uint256 executableAtTime,
uint256 intentionTime,
bytes32 trackingCode
) external;
function deletePosition(address) external;
function deleteDelayedOrder(address) external;
function flag(address account, address flagger) external;
function unflag(address) external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
pragma experimental ABIEncoderV2;
import "./IPerpsV2MarketBaseTypes.sol";
interface IPerpsV2MarketViews {
/* ---------- Market Details ---------- */
function marketKey() external view returns (bytes32 key);
function baseAsset() external view returns (bytes32 key);
function marketSize() external view returns (uint128 size);
function marketSkew() external view returns (int128 skew);
function fundingLastRecomputed() external view returns (uint32 timestamp);
function fundingRateLastRecomputed() external view returns (int128 fundingRate);
function fundingSequence(uint index) external view returns (int128 netFunding);
function positions(address account) external view returns (IPerpsV2MarketBaseTypes.Position memory);
function delayedOrders(address account) external view returns (IPerpsV2MarketBaseTypes.DelayedOrder memory);
function assetPrice() external view returns (uint price, bool invalid);
function fillPrice(int sizeDelta) external view returns (uint price, bool invalid);
function marketSizes() external view returns (uint long, uint short);
function marketDebt() external view returns (uint debt, bool isInvalid);
function currentFundingRate() external view returns (int fundingRate);
function currentFundingVelocity() external view returns (int fundingVelocity);
function unrecordedFunding() external view returns (int funding, bool invalid);
function fundingSequenceLength() external view returns (uint length);
/* ---------- Position Details ---------- */
function notionalValue(address account) external view returns (int value, bool invalid);
function profitLoss(address account) external view returns (int pnl, bool invalid);
function accruedFunding(address account) external view returns (int funding, bool invalid);
function remainingMargin(address account) external view returns (uint marginRemaining, bool invalid);
function accessibleMargin(address account) external view returns (uint marginAccessible, bool invalid);
function liquidationPrice(address account) external view returns (uint price, bool invalid);
function liquidationFee(address account) external view returns (uint);
function isFlagged(address account) external view returns (bool);
function canLiquidate(address account) external view returns (bool);
function orderFee(int sizeDelta, IPerpsV2MarketBaseTypes.OrderType orderType)
external
view
returns (uint fee, bool invalid);
function postTradeDetails(
int sizeDelta,
uint tradePrice,
IPerpsV2MarketBaseTypes.OrderType orderType,
address sender
)
external
view
returns (
uint margin,
int size,
uint price,
uint liqPrice,
uint fee,
IPerpsV2MarketBaseTypes.Status status
);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
pragma experimental ABIEncoderV2;
import "./PythStructs.sol";
// import "@pythnetwork/pyth-sdk-solidity/IPyth.sol";
/// @title Consume prices from the Pyth Network (https://pyth.network/).
/// @dev Please refer to the guidance at https://docs.pyth.network/consumers/best-practices for how to consume prices safely.
/// @author Pyth Data Association
interface IPyth {
/// @dev Emitted when the price feed with `id` has received a fresh update.
/// @param id The Pyth Price Feed ID.
/// @param publishTime Publish time of the given price update.
/// @param price Price of the given price update.
/// @param conf Confidence interval of the given price update.
event PriceFeedUpdate(bytes32 indexed id, uint64 publishTime, int64 price, uint64 conf);
/// @dev Emitted when a batch price update is processed successfully.
/// @param chainId ID of the source chain that the batch price update comes from.
/// @param sequenceNumber Sequence number of the batch price update.
event BatchPriceFeedUpdate(uint16 chainId, uint64 sequenceNumber);
/// @notice Returns the period (in seconds) that a price feed is considered valid since its publish time
function getValidTimePeriod() external view returns (uint validTimePeriod);
/// @notice Returns the price and confidence interval.
/// @dev Reverts if the price has not been updated within the last `getValidTimePeriod()` seconds.
/// @param id The Pyth Price Feed ID of which to fetch the price and confidence interval.
/// @return price - please read the documentation of PythStructs.Price to understand how to use this safely.
function getPrice(bytes32 id) external view returns (PythStructs.Price memory price);
/// @notice Returns the exponentially-weighted moving average price and confidence interval.
/// @dev Reverts if the EMA price is not available.
/// @param id The Pyth Price Feed ID of which to fetch the EMA price and confidence interval.
/// @return price - please read the documentation of PythStructs.Price to understand how to use this safely.
function getEmaPrice(bytes32 id) external view returns (PythStructs.Price memory price);
/// @notice Returns the price of a price feed without any sanity checks.
/// @dev This function returns the most recent price update in this contract without any recency checks.
/// This function is unsafe as the returned price update may be arbitrarily far in the past.
///
/// Users of this function should check the `publishTime` in the price to ensure that the returned price is
/// sufficiently recent for their application. If you are considering using this function, it may be
/// safer / easier to use either `getPrice` or `getPriceNoOlderThan`.
/// @return price - please read the documentation of PythStructs.Price to understand how to use this safely.
function getPriceUnsafe(bytes32 id) external view returns (PythStructs.Price memory price);
/// @notice Returns the price that is no older than `age` seconds of the current time.
/// @dev This function is a sanity-checked version of `getPriceUnsafe` which is useful in
/// applications that require a sufficiently-recent price. Reverts if the price wasn't updated sufficiently
/// recently.
/// @return price - please read the documentation of PythStructs.Price to understand how to use this safely.
function getPriceNoOlderThan(bytes32 id, uint age) external view returns (PythStructs.Price memory price);
/// @notice Returns the exponentially-weighted moving average price of a price feed without any sanity checks.
/// @dev This function returns the same price as `getEmaPrice` in the case where the price is available.
/// However, if the price is not recent this function returns the latest available price.
///
/// The returned price can be from arbitrarily far in the past; this function makes no guarantees that
/// the returned price is recent or useful for any particular application.
///
/// Users of this function should check the `publishTime` in the price to ensure that the returned price is
/// sufficiently recent for their application. If you are considering using this function, it may be
/// safer / easier to use either `getEmaPrice` or `getEmaPriceNoOlderThan`.
/// @return price - please read the documentation of PythStructs.Price to understand how to use this safely.
function getEmaPriceUnsafe(bytes32 id) external view returns (PythStructs.Price memory price);
/// @notice Returns the exponentially-weighted moving average price that is no older than `age` seconds
/// of the current time.
/// @dev This function is a sanity-checked version of `getEmaPriceUnsafe` which is useful in
/// applications that require a sufficiently-recent price. Reverts if the price wasn't updated sufficiently
/// recently.
/// @return price - please read the documentation of PythStructs.Price to understand how to use this safely.
function getEmaPriceNoOlderThan(bytes32 id, uint age) external view returns (PythStructs.Price memory price);
/// @notice Update price feeds with given update messages.
/// This method requires the caller to pay a fee in wei; the required fee can be computed by calling
/// `getUpdateFee` with the length of the `updateData` array.
/// Prices will be updated if they are more recent than the current stored prices.
/// The call will succeed even if the update is not the most recent.
/// @dev Reverts if the transferred fee is not sufficient or the updateData is invalid.
/// @param updateData Array of price update data.
function updatePriceFeeds(bytes[] calldata updateData) external payable;
/// @notice Wrapper around updatePriceFeeds that rejects fast if a price update is not necessary. A price update is
/// necessary if the current on-chain publishTime is older than the given publishTime. It relies solely on the
/// given `publishTimes` for the price feeds and does not read the actual price update publish time within `updateData`.
///
/// This method requires the caller to pay a fee in wei; the required fee can be computed by calling
/// `getUpdateFee` with the length of the `updateData` array.
///
/// `priceIds` and `publishTimes` are two arrays with the same size that correspond to senders known publishTime
/// of each priceId when calling this method. If all of price feeds within `priceIds` have updated and have
/// a newer or equal publish time than the given publish time, it will reject the transaction to save gas.
/// Otherwise, it calls updatePriceFeeds method to update the prices.
///
/// @dev Reverts if update is not needed or the transferred fee is not sufficient or the updateData is invalid.
/// @param updateData Array of price update data.
/// @param priceIds Array of price ids.
/// @param publishTimes Array of publishTimes. `publishTimes[i]` corresponds to known `publishTime` of `priceIds[i]`
function updatePriceFeedsIfNecessary(
bytes[] calldata updateData,
bytes32[] calldata priceIds,
uint64[] calldata publishTimes
) external payable;
/// @notice Returns the required fee to update an array of price updates.
/// @param updateData Array of price update data.
/// @return feeAmount The required fee in Wei.
function getUpdateFee(bytes[] calldata updateData) external view returns (uint feeAmount);
/// @notice Parse `updateData` and return price feeds of the given `priceIds` if they are all published
/// within `minPublishTime` and `maxPublishTime`.
///
/// You can use this method if you want to use a Pyth price at a fixed time and not the most recent price;
/// otherwise, please consider using `updatePriceFeeds`. This method does not store the price updates on-chain.
///
/// This method requires the caller to pay a fee in wei; the required fee can be computed by calling
/// `getUpdateFee` with the length of the `updateData` array.
///
///
/// @dev Reverts if the transferred fee is not sufficient or the updateData is invalid or there is
/// no update for any of the given `priceIds` within the given time range.
/// @param updateData Array of price update data.
/// @param priceIds Array of price ids.
/// @param minPublishTime minimum acceptable publishTime for the given `priceIds`.
/// @param maxPublishTime maximum acceptable publishTime for the given `priceIds`.
/// @return priceFeeds Array of the price feeds corresponding to the given `priceIds` (with the same order).
function parsePriceFeedUpdates(
bytes[] calldata updateData,
bytes32[] calldata priceIds,
uint64 minPublishTime,
uint64 maxPublishTime
) external payable returns (PythStructs.PriceFeed[] memory priceFeeds);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
// https://docs.synthetix.io/contracts/source/interfaces/irewardescrow
interface IRewardEscrow {
// Views
function balanceOf(address account) external view returns (uint);
function numVestingEntries(address account) external view returns (uint);
function totalEscrowedAccountBalance(address account) external view returns (uint);
function totalVestedAccountBalance(address account) external view returns (uint);
function getVestingScheduleEntry(address account, uint index) external view returns (uint[2] memory);
function getNextVestingIndex(address account) external view returns (uint);
// Mutative functions
function appendVestingEntry(address account, uint quantity) external;
function vest() external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
pragma experimental ABIEncoderV2;
import "./IRewardEscrowV2Frozen.sol";
interface IRewardEscrowV2Storage {
/// Views
function numVestingEntries(address account) external view returns (uint);
function totalEscrowedAccountBalance(address account) external view returns (uint);
function totalVestedAccountBalance(address account) external view returns (uint);
function totalEscrowedBalance() external view returns (uint);
function nextEntryId() external view returns (uint);
function vestingSchedules(address account, uint256 entryId) external view returns (VestingEntries.VestingEntry memory);
function accountVestingEntryIDs(address account, uint256 index) external view returns (uint);
/// Mutative
function setZeroAmount(address account, uint entryId) external;
function setZeroAmountUntilTarget(
address account,
uint startIndex,
uint targetAmount
)
external
returns (
uint total,
uint endIndex,
uint lastEntryTime
);
function updateEscrowAccountBalance(address account, int delta) external;
function updateVestedAccountBalance(address account, int delta) external;
function updateTotalEscrowedBalance(int delta) external;
function addVestingEntry(address account, VestingEntries.VestingEntry calldata entry) external returns (uint);
// setFallbackRewardEscrow is used for configuration but not used by contracts
}
/// this should remain backwards compatible to IRewardEscrowV2Frozen
/// ideally this would be done by inheriting from that interface
/// but solidity v0.5 doesn't support interface inheritance
interface IRewardEscrowV2 {
// Views
function balanceOf(address account) external view returns (uint);
function numVestingEntries(address account) external view returns (uint);
function totalEscrowedBalance() external view returns (uint);
function totalEscrowedAccountBalance(address account) external view returns (uint);
function totalVestedAccountBalance(address account) external view returns (uint);
function getVestingQuantity(address account, uint256[] calldata entryIDs) external view returns (uint);
function getVestingSchedules(
address account,
uint256 index,
uint256 pageSize
) external view returns (VestingEntries.VestingEntryWithID[] memory);
function getAccountVestingEntryIDs(
address account,
uint256 index,
uint256 pageSize
) external view returns (uint256[] memory);
function getVestingEntryClaimable(address account, uint256 entryID) external view returns (uint);
function getVestingEntry(address account, uint256 entryID) external view returns (uint64, uint256);
// Mutative functions
function vest(uint256[] calldata entryIDs) external;
function createEscrowEntry(
address beneficiary,
uint256 deposit,
uint256 duration
) external;
function appendVestingEntry(
address account,
uint256 quantity,
uint256 duration
) external;
function migrateVestingSchedule(address _addressToMigrate) external;
function migrateAccountEscrowBalances(
address[] calldata accounts,
uint256[] calldata escrowBalances,
uint256[] calldata vestedBalances
) external;
// Account Merging
function startMergingWindow() external;
function mergeAccount(address accountToMerge, uint256[] calldata entryIDs) external;
function nominateAccountToMerge(address account) external;
function accountMergingIsOpen() external view returns (bool);
// L2 Migration
function importVestingEntries(
address account,
uint256 escrowedAmount,
VestingEntries.VestingEntry[] calldata vestingEntries
) external;
// Return amount of SNX transfered to SynthetixBridgeToOptimism deposit contract
function burnForMigration(address account, uint256[] calldata entryIDs)
external
returns (uint256 escrowedAccountBalance, VestingEntries.VestingEntry[] memory vestingEntries);
function nextEntryId() external view returns (uint);
function vestingSchedules(address account, uint256 entryId) external view returns (VestingEntries.VestingEntry memory);
function accountVestingEntryIDs(address account, uint256 index) external view returns (uint);
/// below are methods not available in IRewardEscrowV2Frozen
// revoke entries for liquidations (access controlled to Synthetix)
function revokeFrom(
address account,
address recipient,
uint targetAmount,
uint startIndex
) external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity 0.5.16;
pragma experimental ABIEncoderV2;
library VestingEntries {
struct VestingEntry {
uint64 endTime;
uint256 escrowAmount;
}
struct VestingEntryWithID {
uint64 endTime;
uint256 escrowAmount;
uint256 entryID;
}
}
/// SIP-252: this is the interface for immutable V2 escrow (renamed with suffix Frozen).
/// These sources need to exist here and match on-chain frozen contracts for tests and reference.
/// the reason for the naming mess is that the immutable LiquidatorRewards expects a working
/// RewardEscrowV2 resolver entry for its getReward method, so the "new" (would be V3)
/// needs to be found at that entry for liq-rewards to function.
interface IRewardEscrowV2Frozen {
// Views
function balanceOf(address account) external view returns (uint);
function numVestingEntries(address account) external view returns (uint);
function totalEscrowedBalance() external view returns (uint);
function totalEscrowedAccountBalance(address account) external view returns (uint);
function totalVestedAccountBalance(address account) external view returns (uint);
function getVestingQuantity(address account, uint256[] calldata entryIDs) external view returns (uint);
function getVestingSchedules(
address account,
uint256 index,
uint256 pageSize
) external view returns (VestingEntries.VestingEntryWithID[] memory);
function getAccountVestingEntryIDs(
address account,
uint256 index,
uint256 pageSize
) external view returns (uint256[] memory);
function getVestingEntryClaimable(address account, uint256 entryID) external view returns (uint);
function getVestingEntry(address account, uint256 entryID) external view returns (uint64, uint256);
// Mutative functions
function vest(uint256[] calldata entryIDs) external;
function createEscrowEntry(
address beneficiary,
uint256 deposit,
uint256 duration
) external;
function appendVestingEntry(
address account,
uint256 quantity,
uint256 duration
) external;
function migrateVestingSchedule(address _addressToMigrate) external;
function migrateAccountEscrowBalances(
address[] calldata accounts,
uint256[] calldata escrowBalances,
uint256[] calldata vestedBalances
) external;
// Account Merging
function startMergingWindow() external;
function mergeAccount(address accountToMerge, uint256[] calldata entryIDs) external;
function nominateAccountToMerge(address account) external;
function accountMergingIsOpen() external view returns (bool);
// L2 Migration
function importVestingEntries(
address account,
uint256 escrowedAmount,
VestingEntries.VestingEntry[] calldata vestingEntries
) external;
// Return amount of SNX transfered to SynthetixBridgeToOptimism deposit contract
function burnForMigration(address account, uint256[] calldata entryIDs)
external
returns (uint256 escrowedAccountBalance, VestingEntries.VestingEntry[] memory vestingEntries);
function nextEntryId() external view returns (uint);
function vestingSchedules(address account, uint256 entryId) external view returns (VestingEntries.VestingEntry memory);
function accountVestingEntryIDs(address account, uint256 index) external view returns (uint);
//function totalEscrowedAccountBalance(address account) external view returns (uint);
//function totalVestedAccountBalance(address account) external view returns (uint);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
// https://docs.synthetix.io/contracts/source/interfaces/irewardsdistribution
interface IRewardsDistribution {
// Structs
struct DistributionData {
address destination;
uint amount;
}
// Views
function authority() external view returns (address);
function distributions(uint index) external view returns (address destination, uint amount); // DistributionData
function distributionsLength() external view returns (uint);
// Mutative Functions
function distributeRewards(uint amount) external returns (bool);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
// https://docs.synthetix.io/contracts/source/interfaces/istakingrewards
interface IShortingRewards {
// Views
function lastTimeRewardApplicable() external view returns (uint256);
function rewardPerToken() external view returns (uint256);
function earned(address account) external view returns (uint256);
function getRewardForDuration() external view returns (uint256);
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
// Mutative
function enrol(address account, uint256 amount) external;
function withdraw(address account, uint256 amount) external;
function getReward(address account) external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
// Inheritance
import "./Owned.sol";
import "./MixinResolver.sol";
import "./MixinSystemSettings.sol";
import "./IIssuer.sol";
// Libraries
import "./SafeCast.sol";
import "./SafeDecimalMath.sol";
// Internal references
import "./ISynth.sol";
import "./ISynthetixDebtShare.sol";
import "./IExchanger.sol";
import "./IDelegateApprovals.sol";
import "./IExchangeRates.sol";
import "./ICircuitBreaker.sol";
import "./IHasBalance.sol";
import "./IERC20.sol";
import "./ILiquidator.sol";
import "./ILiquidatorRewards.sol";
import "./ISynthRedeemer.sol";
import "./ISystemStatus.sol";
import "./Proxyable.sol";
import "./AggregatorV2V3Interface.sol";
interface IProxy {
function target() external view returns (address);
}
interface IIssuerInternalDebtCache {
function updateCachedSynthDebtWithRate(bytes32 currencyKey, uint currencyRate) external;
function updateCachedSynthDebtsWithRates(bytes32[] calldata currencyKeys, uint[] calldata currencyRates) external;
function updateDebtCacheValidity(bool currentlyInvalid) external;
function totalNonSnxBackedDebt() external view returns (uint excludedDebt, bool isInvalid);
function cacheInfo()
external
view
returns (
uint cachedDebt,
uint timestamp,
bool isInvalid,
bool isStale
);
function updateCachedsUSDDebt(int amount) external;
}
// https://docs.synthetix.io/contracts/source/contracts/issuer
contract Issuer is Owned, MixinSystemSettings, IIssuer {
using SafeMath for uint;
using SafeDecimalMath for uint;
bytes32 public constant CONTRACT_NAME = "Issuer";
// Available Synths which can be used with the system
ISynth[] public availableSynths;
mapping(bytes32 => ISynth) public synths;
mapping(address => bytes32) public synthsByAddress;
/* ========== ENCODED NAMES ========== */
bytes32 internal constant sUSD = "sUSD";
bytes32 internal constant SNX = "SNX";
// Flexible storage names
bytes32 internal constant LAST_ISSUE_EVENT = "lastIssueEvent";
/* ========== ADDRESS RESOLVER CONFIGURATION ========== */
bytes32 private constant CONTRACT_SYNTHETIX = "Synthetix";
bytes32 private constant CONTRACT_EXCHANGER = "Exchanger";
bytes32 private constant CONTRACT_EXRATES = "ExchangeRates";
bytes32 private constant CONTRACT_CIRCUIT_BREAKER = "CircuitBreaker";
bytes32 private constant CONTRACT_SYNTHETIXDEBTSHARE = "SynthetixDebtShare";
bytes32 private constant CONTRACT_FEEPOOL = "FeePool";
bytes32 private constant CONTRACT_DELEGATEAPPROVALS = "DelegateApprovals";
bytes32 private constant CONTRACT_REWARDESCROW_V2 = "RewardEscrowV2";
bytes32 private constant CONTRACT_LIQUIDATOR = "Liquidator";
bytes32 private constant CONTRACT_LIQUIDATOR_REWARDS = "LiquidatorRewards";
bytes32 private constant CONTRACT_DEBTCACHE = "DebtCache";
bytes32 private constant CONTRACT_SYNTHREDEEMER = "SynthRedeemer";
bytes32 private constant CONTRACT_SYNTHETIXBRIDGETOOPTIMISM = "SynthetixBridgeToOptimism";
bytes32 private constant CONTRACT_SYNTHETIXBRIDGETOBASE = "SynthetixBridgeToBase";
bytes32 private constant CONTRACT_DEBT_MIGRATOR_ON_ETHEREUM = "DebtMigratorOnEthereum";
bytes32 private constant CONTRACT_DEBT_MIGRATOR_ON_OPTIMISM = "DebtMigratorOnOptimism";
bytes32 private constant CONTRACT_EXT_AGGREGATOR_ISSUED_SYNTHS = "ext:AggregatorIssuedSynths";
bytes32 private constant CONTRACT_EXT_AGGREGATOR_DEBT_RATIO = "ext:AggregatorDebtRatio";
constructor(address _owner, address _resolver) public Owned(_owner) MixinSystemSettings(_resolver) {}
/* ========== VIEWS ========== */
function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
bytes32[] memory existingAddresses = MixinSystemSettings.resolverAddressesRequired();
bytes32[] memory newAddresses = new bytes32[](14);
newAddresses[0] = CONTRACT_SYNTHETIX;
newAddresses[1] = CONTRACT_EXCHANGER;
newAddresses[2] = CONTRACT_EXRATES;
newAddresses[3] = CONTRACT_CIRCUIT_BREAKER;
newAddresses[4] = CONTRACT_SYNTHETIXDEBTSHARE;
newAddresses[5] = CONTRACT_FEEPOOL;
newAddresses[6] = CONTRACT_DELEGATEAPPROVALS;
newAddresses[7] = CONTRACT_REWARDESCROW_V2;
newAddresses[8] = CONTRACT_LIQUIDATOR;
newAddresses[9] = CONTRACT_LIQUIDATOR_REWARDS;
newAddresses[10] = CONTRACT_DEBTCACHE;
newAddresses[11] = CONTRACT_SYNTHREDEEMER;
newAddresses[12] = CONTRACT_EXT_AGGREGATOR_ISSUED_SYNTHS;
newAddresses[13] = CONTRACT_EXT_AGGREGATOR_DEBT_RATIO;
return combineArrays(existingAddresses, newAddresses);
}
function synthetixERC20() internal view returns (IERC20) {
return IERC20(requireAndGetAddress(CONTRACT_SYNTHETIX));
}
function exchanger() internal view returns (IExchanger) {
return IExchanger(requireAndGetAddress(CONTRACT_EXCHANGER));
}
function exchangeRates() internal view returns (IExchangeRates) {
return IExchangeRates(requireAndGetAddress(CONTRACT_EXRATES));
}
function circuitBreaker() internal view returns (ICircuitBreaker) {
return ICircuitBreaker(requireAndGetAddress(CONTRACT_CIRCUIT_BREAKER));
}
function synthetixDebtShare() internal view returns (ISynthetixDebtShare) {
return ISynthetixDebtShare(requireAndGetAddress(CONTRACT_SYNTHETIXDEBTSHARE));
}
function liquidator() internal view returns (ILiquidator) {
return ILiquidator(requireAndGetAddress(CONTRACT_LIQUIDATOR));
}
function liquidatorRewards() internal view returns (ILiquidatorRewards) {
return ILiquidatorRewards(requireAndGetAddress(CONTRACT_LIQUIDATOR_REWARDS));
}
function delegateApprovals() internal view returns (IDelegateApprovals) {
return IDelegateApprovals(requireAndGetAddress(CONTRACT_DELEGATEAPPROVALS));
}
function rewardEscrowV2() internal view returns (IHasBalance) {
return IHasBalance(requireAndGetAddress(CONTRACT_REWARDESCROW_V2));
}
function debtCache() internal view returns (IIssuerInternalDebtCache) {
return IIssuerInternalDebtCache(requireAndGetAddress(CONTRACT_DEBTCACHE));
}
function synthRedeemer() internal view returns (ISynthRedeemer) {
return ISynthRedeemer(requireAndGetAddress(CONTRACT_SYNTHREDEEMER));
}
function allNetworksDebtInfo()
public
view
returns (
uint256 debt,
uint256 sharesSupply,
bool isStale
)
{
(, int256 rawIssuedSynths, , uint issuedSynthsUpdatedAt, ) =
_latestRoundData(requireAndGetAddress(CONTRACT_EXT_AGGREGATOR_ISSUED_SYNTHS));
(uint rawRatio, uint ratioUpdatedAt) = _rawDebtRatioAndUpdatedAt();
debt = uint(rawIssuedSynths);
sharesSupply = rawRatio == 0 ? 0 : debt.divideDecimalRoundPrecise(uint(rawRatio));
uint stalePeriod = getRateStalePeriod();
isStale =
stalePeriod < block.timestamp &&
(block.timestamp - stalePeriod > issuedSynthsUpdatedAt || block.timestamp - stalePeriod > ratioUpdatedAt);
}
function issuanceRatio() external view returns (uint) {
return getIssuanceRatio();
}
function _rateAndInvalid(bytes32 currencyKey) internal view returns (uint, bool) {
return exchangeRates().rateAndInvalid(currencyKey);
}
function _latestRoundData(address aggregator)
internal
view
returns (
uint80,
int256,
uint256,
uint256,
uint80
)
{
return AggregatorV2V3Interface(aggregator).latestRoundData();
}
function _rawDebtRatioAndUpdatedAt() internal view returns (uint, uint) {
(, int256 rawRatioInt, , uint ratioUpdatedAt, ) =
_latestRoundData(requireAndGetAddress(CONTRACT_EXT_AGGREGATOR_DEBT_RATIO));
return (uint(rawRatioInt), ratioUpdatedAt);
}
function _sharesForDebt(uint debtAmount) internal view returns (uint) {
(uint rawRatio, ) = _rawDebtRatioAndUpdatedAt();
return rawRatio == 0 ? 0 : debtAmount.divideDecimalRoundPrecise(rawRatio);
}
function _debtForShares(uint sharesAmount) internal view returns (uint) {
(uint rawRatio, ) = _rawDebtRatioAndUpdatedAt();
return sharesAmount.multiplyDecimalRoundPrecise(rawRatio);
}
function _debtShareBalanceOf(address account) internal view returns (uint) {
return synthetixDebtShare().balanceOf(account);
}
function _snxBalanceOf(address account) internal view returns (uint) {
return synthetixERC20().balanceOf(account);
}
function _rewardEscrowBalanceOf(address account) internal view returns (uint) {
return rewardEscrowV2().balanceOf(account);
}
function _availableCurrencyKeysWithOptionalSNX(bool withSNX) internal view returns (bytes32[] memory) {
bytes32[] memory currencyKeys = new bytes32[](availableSynths.length + (withSNX ? 1 : 0));
for (uint i = 0; i < availableSynths.length; i++) {
currencyKeys[i] = synthsByAddress[address(availableSynths[i])];
}
if (withSNX) {
currencyKeys[availableSynths.length] = SNX;
}
return currencyKeys;
}
// Returns the total value of the debt pool in currency specified by `currencyKey`.
// To return only the SNX-backed debt, set `excludeCollateral` to true.
function _totalIssuedSynths(bytes32 currencyKey, bool excludeCollateral)
internal
view
returns (uint totalIssued, bool anyRateIsInvalid)
{
(uint debt, , bool cacheIsInvalid, bool cacheIsStale) = debtCache().cacheInfo();
anyRateIsInvalid = cacheIsInvalid || cacheIsStale;
// Add total issued synths from non snx collateral back into the total if not excluded
if (!excludeCollateral) {
(uint nonSnxDebt, bool invalid) = debtCache().totalNonSnxBackedDebt();
debt = debt.add(nonSnxDebt);
anyRateIsInvalid = anyRateIsInvalid || invalid;
}
if (currencyKey == sUSD) {
return (debt, anyRateIsInvalid);
}
(uint currencyRate, bool currencyRateInvalid) = _rateAndInvalid(currencyKey);
return (debt.divideDecimalRound(currencyRate), anyRateIsInvalid || currencyRateInvalid);
}
function _debtBalanceOfAndTotalDebt(uint debtShareBalance, bytes32 currencyKey)
internal
view
returns (
uint debtBalance,
uint totalSystemValue,
bool anyRateIsInvalid
)
{
// What's the total value of the system excluding ETH backed synths in their requested currency?
(uint snxBackedAmount, , bool debtInfoStale) = allNetworksDebtInfo();
if (debtShareBalance == 0) {
return (0, snxBackedAmount, debtInfoStale);
}
// existing functionality requires for us to convert into the exchange rate specified by `currencyKey`
(uint currencyRate, bool currencyRateInvalid) = _rateAndInvalid(currencyKey);
debtBalance = _debtForShares(debtShareBalance).divideDecimalRound(currencyRate);
totalSystemValue = snxBackedAmount;
anyRateIsInvalid = currencyRateInvalid || debtInfoStale;
}
function _canBurnSynths(address account) internal view returns (bool) {
return now >= _lastIssueEvent(account).add(getMinimumStakeTime());
}
function _lastIssueEvent(address account) internal view returns (uint) {
// Get the timestamp of the last issue this account made
return flexibleStorage().getUIntValue(CONTRACT_NAME, keccak256(abi.encodePacked(LAST_ISSUE_EVENT, account)));
}
function _remainingIssuableSynths(address _issuer)
internal
view
returns (
uint maxIssuable,
uint alreadyIssued,
uint totalSystemDebt,
bool anyRateIsInvalid
)
{
(alreadyIssued, totalSystemDebt, anyRateIsInvalid) = _debtBalanceOfAndTotalDebt(_debtShareBalanceOf(_issuer), sUSD);
(uint issuable, bool isInvalid) = _maxIssuableSynths(_issuer);
maxIssuable = issuable;
anyRateIsInvalid = anyRateIsInvalid || isInvalid;
if (alreadyIssued >= maxIssuable) {
maxIssuable = 0;
} else {
maxIssuable = maxIssuable.sub(alreadyIssued);
}
}
function _snxToUSD(uint amount, uint snxRate) internal pure returns (uint) {
return amount.multiplyDecimalRound(snxRate);
}
function _usdToSnx(uint amount, uint snxRate) internal pure returns (uint) {
return amount.divideDecimalRound(snxRate);
}
function _maxIssuableSynths(address _issuer) internal view returns (uint, bool) {
// What is the value of their SNX balance in sUSD
(uint snxRate, bool isInvalid) = _rateAndInvalid(SNX);
uint destinationValue = _snxToUSD(_collateral(_issuer), snxRate);
// They're allowed to issue up to issuanceRatio of that value
return (destinationValue.multiplyDecimal(getIssuanceRatio()), isInvalid);
}
function _collateralisationRatio(address _issuer) internal view returns (uint, bool) {
uint totalOwnedSynthetix = _collateral(_issuer);
(uint debtBalance, , bool anyRateIsInvalid) = _debtBalanceOfAndTotalDebt(_debtShareBalanceOf(_issuer), SNX);
// it's more gas intensive to put this check here if they have 0 SNX, but it complies with the interface
if (totalOwnedSynthetix == 0) return (0, anyRateIsInvalid);
return (debtBalance.divideDecimalRound(totalOwnedSynthetix), anyRateIsInvalid);
}
function _collateral(address account) internal view returns (uint) {
return _snxBalanceOf(account).add(_rewardEscrowBalanceOf(account)).add(liquidatorRewards().earned(account));
}
function minimumStakeTime() external view returns (uint) {
return getMinimumStakeTime();
}
function canBurnSynths(address account) external view returns (bool) {
return _canBurnSynths(account);
}
function availableCurrencyKeys() external view returns (bytes32[] memory) {
return _availableCurrencyKeysWithOptionalSNX(false);
}
function availableSynthCount() external view returns (uint) {
return availableSynths.length;
}
function anySynthOrSNXRateIsInvalid() external view returns (bool anyRateInvalid) {
(, anyRateInvalid) = exchangeRates().ratesAndInvalidForCurrencies(_availableCurrencyKeysWithOptionalSNX(true));
}
function totalIssuedSynths(bytes32 currencyKey, bool excludeOtherCollateral) external view returns (uint totalIssued) {
(totalIssued, ) = _totalIssuedSynths(currencyKey, excludeOtherCollateral);
}
function lastIssueEvent(address account) external view returns (uint) {
return _lastIssueEvent(account);
}
function collateralisationRatio(address _issuer) external view returns (uint cratio) {
(cratio, ) = _collateralisationRatio(_issuer);
}
function collateralisationRatioAndAnyRatesInvalid(address _issuer)
external
view
returns (uint cratio, bool anyRateIsInvalid)
{
return _collateralisationRatio(_issuer);
}
function collateral(address account) external view returns (uint) {
return _collateral(account);
}
function debtBalanceOf(address _issuer, bytes32 currencyKey) external view returns (uint debtBalance) {
// What was their initial debt ownership?
uint debtShareBalance = _debtShareBalanceOf(_issuer);
// If it's zero, they haven't issued, and they have no debt.
if (debtShareBalance == 0) return 0;
(debtBalance, , ) = _debtBalanceOfAndTotalDebt(debtShareBalance, currencyKey);
}
function remainingIssuableSynths(address _issuer)
external
view
returns (
uint maxIssuable,
uint alreadyIssued,
uint totalSystemDebt
)
{
(maxIssuable, alreadyIssued, totalSystemDebt, ) = _remainingIssuableSynths(_issuer);
}
function maxIssuableSynths(address _issuer) external view returns (uint) {
(uint maxIssuable, ) = _maxIssuableSynths(_issuer);
return maxIssuable;
}
function transferableSynthetixAndAnyRateIsInvalid(address account, uint balance)
external
view
returns (uint transferable, bool anyRateIsInvalid)
{
// How many SNX do they have, excluding escrow?
// Note: We're excluding escrow here because we're interested in their transferable amount
// and escrowed SNX are not transferable.
// How many of those will be locked by the amount they've issued?
// Assuming issuance ratio is 20%, then issuing 20 SNX of value would require
// 100 SNX to be locked in their wallet to maintain their collateralisation ratio
// The locked synthetix value can exceed their balance.
uint debtBalance;
(debtBalance, , anyRateIsInvalid) = _debtBalanceOfAndTotalDebt(_debtShareBalanceOf(account), SNX);
uint lockedSynthetixValue = debtBalance.divideDecimalRound(getIssuanceRatio());
// If we exceed the balance, no SNX are transferable, otherwise the difference is.
if (lockedSynthetixValue >= balance) {
transferable = 0;
} else {
transferable = balance.sub(lockedSynthetixValue);
}
}
function getSynths(bytes32[] calldata currencyKeys) external view returns (ISynth[] memory) {
uint numKeys = currencyKeys.length;
ISynth[] memory addresses = new ISynth[](numKeys);
for (uint i = 0; i < numKeys; i++) {
addresses[i] = synths[currencyKeys[i]];
}
return addresses;
}
/// @notice Provide the results that would be returned by the mutative liquidateAccount() method (that's reserved to Synthetix)
/// @param account The account to be liquidated
/// @param isSelfLiquidation boolean to determine if this is a forced or self-invoked liquidation
/// @return totalRedeemed the total amount of collateral (SNX) to redeem (liquid and escrow)
/// @return debtToRemove the amount of debt (sUSD) to burn in order to fix the account's c-ratio
/// @return escrowToLiquidate the amount of escrow SNX that will be revoked during liquidation
/// @return initialDebtBalance the amount of initial (sUSD) debt the account has
function liquidationAmounts(address account, bool isSelfLiquidation)
external
view
returns (
uint totalRedeemed,
uint debtToRemove,
uint escrowToLiquidate,
uint initialDebtBalance
)
{
return _liquidationAmounts(account, isSelfLiquidation);
}
/* ========== MUTATIVE FUNCTIONS ========== */
function _addSynth(ISynth synth) internal {
bytes32 currencyKey = synth.currencyKey();
require(synths[currencyKey] == ISynth(0), "Synth exists");
require(synthsByAddress[address(synth)] == bytes32(0), "Synth address already exists");
availableSynths.push(synth);
synths[currencyKey] = synth;
synthsByAddress[address(synth)] = currencyKey;
emit SynthAdded(currencyKey, address(synth));
}
function addSynth(ISynth synth) external onlyOwner {
_addSynth(synth);
// Invalidate the cache to force a snapshot to be recomputed. If a synth were to be added
// back to the system and it still somehow had cached debt, this would force the value to be
// updated.
debtCache().updateDebtCacheValidity(true);
}
function addSynths(ISynth[] calldata synthsToAdd) external onlyOwner {
uint numSynths = synthsToAdd.length;
for (uint i = 0; i < numSynths; i++) {
_addSynth(synthsToAdd[i]);
}
// Invalidate the cache to force a snapshot to be recomputed.
debtCache().updateDebtCacheValidity(true);
}
function _removeSynth(bytes32 currencyKey) internal {
address synthToRemove = address(synths[currencyKey]);
require(synthToRemove != address(0), "Synth does not exist");
require(currencyKey != sUSD, "Cannot remove synth");
uint synthSupply = IERC20(synthToRemove).totalSupply();
if (synthSupply > 0) {
(uint amountOfsUSD, uint rateToRedeem, ) =
exchangeRates().effectiveValueAndRates(currencyKey, synthSupply, "sUSD");
require(rateToRedeem > 0, "Cannot remove without rate");
ISynthRedeemer _synthRedeemer = synthRedeemer();
synths[sUSD].issue(address(_synthRedeemer), amountOfsUSD);
// ensure the debt cache is aware of the new sUSD issued
debtCache().updateCachedsUSDDebt(SafeCast.toInt256(amountOfsUSD));
_synthRedeemer.deprecate(IERC20(address(Proxyable(synthToRemove).proxy())), rateToRedeem);
}
// Remove the synth from the availableSynths array.
for (uint i = 0; i < availableSynths.length; i++) {
if (address(availableSynths[i]) == synthToRemove) {
delete availableSynths[i];
// Copy the last synth into the place of the one we just deleted
// If there's only one synth, this is synths[0] = synths[0].
// If we're deleting the last one, it's also a NOOP in the same way.
availableSynths[i] = availableSynths[availableSynths.length - 1];
// Decrease the size of the array by one.
availableSynths.length--;
break;
}
}
// And remove it from the synths mapping
delete synthsByAddress[synthToRemove];
delete synths[currencyKey];
emit SynthRemoved(currencyKey, synthToRemove);
}
function removeSynth(bytes32 currencyKey) external onlyOwner {
// Remove its contribution from the debt pool snapshot, and
// invalidate the cache to force a new snapshot.
IIssuerInternalDebtCache cache = debtCache();
cache.updateCachedSynthDebtWithRate(currencyKey, 0);
cache.updateDebtCacheValidity(true);
_removeSynth(currencyKey);
}
function removeSynths(bytes32[] calldata currencyKeys) external onlyOwner {
uint numKeys = currencyKeys.length;
// Remove their contributions from the debt pool snapshot, and
// invalidate the cache to force a new snapshot.
IIssuerInternalDebtCache cache = debtCache();
uint[] memory zeroRates = new uint[](numKeys);
cache.updateCachedSynthDebtsWithRates(currencyKeys, zeroRates);
cache.updateDebtCacheValidity(true);
for (uint i = 0; i < numKeys; i++) {
_removeSynth(currencyKeys[i]);
}
}
function issueSynthsWithoutDebt(
bytes32 currencyKey,
address to,
uint amount
) external onlyTrustedMinters returns (bool rateInvalid) {
require(address(synths[currencyKey]) != address(0), "synth doesn't exist");
require(amount > 0, "cannot issue 0 synths");
// record issue timestamp
_setLastIssueEvent(to);
// Create their synths
synths[currencyKey].issue(to, amount);
// Account for the issued debt in the cache
(uint rate, bool rateInvalid) = _rateAndInvalid(currencyKey);
debtCache().updateCachedsUSDDebt(SafeCast.toInt256(amount.multiplyDecimal(rate)));
// returned so that the caller can decide what to do if the rate is invalid
return rateInvalid;
}
function burnSynthsWithoutDebt(
bytes32 currencyKey,
address from,
uint amount
) external onlyTrustedMinters returns (bool rateInvalid) {
require(address(synths[currencyKey]) != address(0), "synth doesn't exist");
require(amount > 0, "cannot issue 0 synths");
exchanger().settle(from, currencyKey);
// Burn some synths
synths[currencyKey].burn(from, amount);
// Account for the burnt debt in the cache. If rate is invalid, the user won't be able to exchange
(uint rate, bool rateInvalid) = _rateAndInvalid(currencyKey);
debtCache().updateCachedsUSDDebt(-SafeCast.toInt256(amount.multiplyDecimal(rate)));
// returned so that the caller can decide what to do if the rate is invalid
return rateInvalid;
}
/**
* SIP-237: Debt Migration
* Function used for the one-way migration of all debt and liquid + escrowed SNX from L1 -> L2
* @param account The address of the account that is being migrated
* @param amount The amount of debt shares moving across layers
*/
function modifyDebtSharesForMigration(address account, uint amount) external onlyTrustedMigrators {
ISynthetixDebtShare sds = synthetixDebtShare();
if (msg.sender == resolver.getAddress(CONTRACT_DEBT_MIGRATOR_ON_ETHEREUM)) {
sds.burnShare(account, amount);
} else if (msg.sender == resolver.getAddress(CONTRACT_DEBT_MIGRATOR_ON_OPTIMISM)) {
sds.mintShare(account, amount);
}
}
/**
* Function used to migrate balances from the CollateralShort contract
* @param short The address of the CollateralShort contract to be upgraded
* @param amount The amount of sUSD collateral to be burnt
*/
function upgradeCollateralShort(address short, uint amount) external onlyOwner {
require(short == resolver.getAddress("CollateralShortLegacy"), "wrong address");
require(amount > 0, "cannot burn 0 synths");
exchanger().settle(short, sUSD);
synths[sUSD].burn(short, amount);
}
function issueSynths(address from, uint amount) external onlySynthetix {
require(amount > 0, "cannot issue 0 synths");
_issueSynths(from, amount, false);
}
function issueMaxSynths(address from) external onlySynthetix {
_issueSynths(from, 0, true);
}
function issueSynthsOnBehalf(
address issueForAddress,
address from,
uint amount
) external onlySynthetix {
_requireCanIssueOnBehalf(issueForAddress, from);
_issueSynths(issueForAddress, amount, false);
}
function issueMaxSynthsOnBehalf(address issueForAddress, address from) external onlySynthetix {
_requireCanIssueOnBehalf(issueForAddress, from);
_issueSynths(issueForAddress, 0, true);
}
function burnSynths(address from, uint amount) external onlySynthetix {
_voluntaryBurnSynths(from, amount, false);
}
function burnSynthsOnBehalf(
address burnForAddress,
address from,
uint amount
) external onlySynthetix {
_requireCanBurnOnBehalf(burnForAddress, from);
_voluntaryBurnSynths(burnForAddress, amount, false);
}
function burnSynthsToTarget(address from) external onlySynthetix {
_voluntaryBurnSynths(from, 0, true);
}
function burnSynthsToTargetOnBehalf(address burnForAddress, address from) external onlySynthetix {
_requireCanBurnOnBehalf(burnForAddress, from);
_voluntaryBurnSynths(burnForAddress, 0, true);
}
function burnForRedemption(
address deprecatedSynthProxy,
address account,
uint balance
) external onlySynthRedeemer {
ISynth(IProxy(deprecatedSynthProxy).target()).burn(account, balance);
}
// SIP-148: Upgraded Liquidation Mechanism
/// @notice This is where the core internal liquidation logic resides. This function can only be invoked by Synthetix.
/// Reverts if liquidator().isLiquidationOpen() returns false (e.g. c-ratio is too high, delay hasn't passed,
/// account wasn't flagged etc)
/// @param account The account to be liquidated
/// @param isSelfLiquidation boolean to determine if this is a forced or self-invoked liquidation
/// @return totalRedeemed the total amount of collateral (SNX) to redeem (liquid and escrow)
/// @return debtRemoved the amount of debt (sUSD) to burn in order to fix the account's c-ratio
/// @return escrowToLiquidate the amount of escrow SNX that will be revoked during liquidation
function liquidateAccount(address account, bool isSelfLiquidation)
external
onlySynthetix
returns (
uint totalRedeemed,
uint debtRemoved,
uint escrowToLiquidate
)
{
require(liquidator().isLiquidationOpen(account, isSelfLiquidation), "Not open for liquidation");
// liquidationAmounts checks isLiquidationOpen for the account
uint initialDebtBalance;
(totalRedeemed, debtRemoved, escrowToLiquidate, initialDebtBalance) = _liquidationAmounts(
account,
isSelfLiquidation
);
// Reduce debt shares by amount to liquidate.
_removeFromDebtRegister(account, debtRemoved, initialDebtBalance);
if (!isSelfLiquidation) {
// In case of forced liquidation only, remove the liquidation flag.
liquidator().removeAccountInLiquidation(account);
}
// Note: To remove the flag after self liquidation, burn to target and then call Liquidator.checkAndRemoveAccountInLiquidation(account).
}
function _liquidationAmounts(address account, bool isSelfLiquidation)
internal
view
returns (
uint totalRedeemed,
uint debtToRemove,
uint escrowToLiquidate,
uint debtBalance
)
{
// Get the account's debt balance
bool anyRateIsInvalid;
(debtBalance, , anyRateIsInvalid) = _debtBalanceOfAndTotalDebt(_debtShareBalanceOf(account), sUSD);
// Get the SNX rate
(uint snxRate, bool snxRateInvalid) = _rateAndInvalid(SNX);
_requireRatesNotInvalid(anyRateIsInvalid || snxRateInvalid);
uint penalty;
if (isSelfLiquidation) {
// Get self liquidation penalty
penalty = getSelfLiquidationPenalty();
// Calculate the amount of debt to remove and SNX to redeem for a self liquidation
debtToRemove = liquidator().calculateAmountToFixCollateral(
debtBalance,
_snxToUSD(_collateral(account), snxRate),
penalty
);
// Get the minimum values for both totalRedeemed and debtToRemove
totalRedeemed = _getMinValue(
_usdToSnx(debtToRemove, snxRate).multiplyDecimal(SafeDecimalMath.unit().add(penalty)),
_snxBalanceOf(account)
);
debtToRemove = _getMinValue(
_snxToUSD(totalRedeemed, snxRate).divideDecimal(SafeDecimalMath.unit().add(penalty)),
debtToRemove
);
// Return escrow as zero since it cannot be self liquidated
return (totalRedeemed, debtToRemove, 0, debtBalance);
} else {
// In the case of forced Liquidation
// Get the forced liquidation penalty and sum of the flag and liquidate rewards.
penalty = getSnxLiquidationPenalty();
uint rewardsSum = getLiquidateReward().add(getFlagReward());
// Get the total USD value of their SNX collateral (including escrow and rewards minus the flag and liquidate rewards)
uint collateralForAccountUSD = _snxToUSD(_collateral(account).sub(rewardsSum), snxRate);
// Calculate the amount of debt to remove and the sUSD value of the SNX required to liquidate.
debtToRemove = liquidator().calculateAmountToFixCollateral(debtBalance, collateralForAccountUSD, penalty);
uint redeemTarget = _usdToSnx(debtToRemove, snxRate).multiplyDecimal(SafeDecimalMath.unit().add(penalty));
if (redeemTarget.add(rewardsSum) >= _collateral(account)) {
// need to wipe out the account
debtToRemove = debtBalance;
totalRedeemed = _collateral(account).sub(rewardsSum);
escrowToLiquidate = _rewardEscrowBalanceOf(account);
return (totalRedeemed, debtToRemove, escrowToLiquidate, debtBalance);
} else {
// normal forced liquidation
(totalRedeemed, escrowToLiquidate) = _redeemableCollateralForTarget(account, redeemTarget, rewardsSum);
return (totalRedeemed, debtToRemove, escrowToLiquidate, debtBalance);
}
}
}
// SIP-252
// calculates the amount of SNX that can be force liquidated (redeemed)
// for the various cases of transferrable & escrowed collateral
function _redeemableCollateralForTarget(
address account,
uint redeemTarget,
uint rewardsSum
) internal view returns (uint totalRedeemed, uint escrowToLiquidate) {
// The balanceOf here can be considered "transferable" since it's not escrowed,
// and it is the only SNX that can potentially be transfered if unstaked.
uint transferable = _snxBalanceOf(account);
if (redeemTarget.add(rewardsSum) <= transferable) {
// transferable is enough
return (redeemTarget, 0);
} else {
// if transferable is not enough
// need only part of the escrow, add the needed part to redeemed
escrowToLiquidate = redeemTarget.add(rewardsSum).sub(transferable);
return (redeemTarget, escrowToLiquidate);
}
}
function _getMinValue(uint x, uint y) internal pure returns (uint) {
return x < y ? x : y;
}
function setCurrentPeriodId(uint128 periodId) external {
require(msg.sender == requireAndGetAddress(CONTRACT_FEEPOOL), "Must be fee pool");
ISynthetixDebtShare sds = synthetixDebtShare();
if (sds.currentPeriodId() < periodId) {
sds.takeSnapshot(periodId);
}
}
/* ========== INTERNAL FUNCTIONS ========== */
function _requireRatesNotInvalid(bool anyRateIsInvalid) internal pure {
require(!anyRateIsInvalid, "A synth or SNX rate is invalid");
}
function _requireCanIssueOnBehalf(address issueForAddress, address from) internal view {
require(delegateApprovals().canIssueFor(issueForAddress, from), "Not approved to act on behalf");
}
function _requireCanBurnOnBehalf(address burnForAddress, address from) internal view {
require(delegateApprovals().canBurnFor(burnForAddress, from), "Not approved to act on behalf");
}
function _issueSynths(
address from,
uint amount,
bool issueMax
) internal {
if (_verifyCircuitBreakers()) {
return;
}
(uint maxIssuable, , , bool anyRateIsInvalid) = _remainingIssuableSynths(from);
_requireRatesNotInvalid(anyRateIsInvalid);
if (!issueMax) {
require(amount <= maxIssuable, "Amount too large");
} else {
amount = maxIssuable;
}
// Keep track of the debt they're about to create
_addToDebtRegister(from, amount);
// record issue timestamp
_setLastIssueEvent(from);
// Create their synths
synths[sUSD].issue(from, amount);
// Account for the issued debt in the cache
debtCache().updateCachedsUSDDebt(SafeCast.toInt256(amount));
}
function _burnSynths(
address debtAccount,
address burnAccount,
uint amount,
uint existingDebt
) internal returns (uint amountBurnt) {
if (_verifyCircuitBreakers()) {
return 0;
}
// liquidation requires sUSD to be already settled / not in waiting period
// If they're trying to burn more debt than they actually owe, rather than fail the transaction, let's just
// clear their debt and leave them be.
amountBurnt = existingDebt < amount ? existingDebt : amount;
// Remove liquidated debt from the ledger
_removeFromDebtRegister(debtAccount, amountBurnt, existingDebt);
// synth.burn does a safe subtraction on balance (so it will revert if there are not enough synths).
synths[sUSD].burn(burnAccount, amountBurnt);
// Account for the burnt debt in the cache.
debtCache().updateCachedsUSDDebt(-SafeCast.toInt256(amountBurnt));
}
// If burning to target, `amount` is ignored, and the correct quantity of sUSD is burnt to reach the target
// c-ratio, allowing fees to be claimed. In this case, pending settlements will be skipped as the user
// will still have debt remaining after reaching their target.
function _voluntaryBurnSynths(
address from,
uint amount,
bool burnToTarget
) internal {
if (_verifyCircuitBreakers()) {
return;
}
if (!burnToTarget) {
// If not burning to target, then burning requires that the minimum stake time has elapsed.
require(_canBurnSynths(from), "Minimum stake time not reached");
// First settle anything pending into sUSD as burning or issuing impacts the size of the debt pool
(, uint refunded, uint numEntriesSettled) = exchanger().settle(from, sUSD);
if (numEntriesSettled > 0) {
amount = exchanger().calculateAmountAfterSettlement(from, sUSD, amount, refunded);
}
}
(uint existingDebt, , bool anyRateIsInvalid) = _debtBalanceOfAndTotalDebt(_debtShareBalanceOf(from), sUSD);
(uint maxIssuableSynthsForAccount, bool snxRateInvalid) = _maxIssuableSynths(from);
_requireRatesNotInvalid(anyRateIsInvalid || snxRateInvalid);
require(existingDebt > 0, "No debt to forgive");
if (burnToTarget) {
amount = existingDebt.sub(maxIssuableSynthsForAccount);
}
uint amountBurnt = _burnSynths(from, from, amount, existingDebt);
// Check and remove liquidation if existingDebt after burning is <= maxIssuableSynths
// Issuance ratio is fixed so should remove any liquidations
if (existingDebt.sub(amountBurnt) <= maxIssuableSynthsForAccount) {
liquidator().removeAccountInLiquidation(from);
}
}
function _setLastIssueEvent(address account) internal {
// Set the timestamp of the last issueSynths
flexibleStorage().setUIntValue(
CONTRACT_NAME,
keccak256(abi.encodePacked(LAST_ISSUE_EVENT, account)),
block.timestamp
);
}
function _addToDebtRegister(address from, uint amount) internal {
// important: this has to happen before any updates to user's debt shares
liquidatorRewards().updateEntry(from);
ISynthetixDebtShare sds = synthetixDebtShare();
// it is possible (eg in tests, system initialized with extra debt) to have issued debt without any shares issued
// in which case, the first account to mint gets the debt. yw.
uint debtShares = _sharesForDebt(amount);
if (debtShares == 0) {
sds.mintShare(from, amount);
} else {
sds.mintShare(from, debtShares);
}
}
function _removeFromDebtRegister(
address from,
uint debtToRemove,
uint existingDebt
) internal {
// important: this has to happen before any updates to user's debt shares
liquidatorRewards().updateEntry(from);
ISynthetixDebtShare sds = synthetixDebtShare();
uint currentDebtShare = _debtShareBalanceOf(from);
if (debtToRemove == existingDebt) {
sds.burnShare(from, currentDebtShare);
} else {
uint sharesToRemove = _sharesForDebt(debtToRemove);
sds.burnShare(from, sharesToRemove < currentDebtShare ? sharesToRemove : currentDebtShare);
}
}
// trips the breaker and returns boolean, where true means the breaker has tripped state
function _verifyCircuitBreakers() internal returns (bool) {
address debtRatioAggregator = requireAndGetAddress(CONTRACT_EXT_AGGREGATOR_DEBT_RATIO);
(, int256 rawRatio, , , ) = AggregatorV2V3Interface(debtRatioAggregator).latestRoundData();
(, bool broken, ) = exchangeRates().rateWithSafetyChecks(SNX);
return circuitBreaker().probeCircuitBreaker(debtRatioAggregator, uint(rawRatio)) || broken;
}
/* ========== MODIFIERS ========== */
modifier onlySynthetix() {
require(msg.sender == address(synthetixERC20()), "Only Synthetix");
_;
}
modifier onlyTrustedMinters() {
address bridgeL1 = resolver.getAddress(CONTRACT_SYNTHETIXBRIDGETOOPTIMISM);
address bridgeL2 = resolver.getAddress(CONTRACT_SYNTHETIXBRIDGETOBASE);
address feePool = resolver.getAddress(CONTRACT_FEEPOOL);
require(msg.sender == bridgeL1 || msg.sender == bridgeL2 || msg.sender == feePool, "only trusted minters");
_;
}
modifier onlyTrustedMigrators() {
address migratorL1 = resolver.getAddress(CONTRACT_DEBT_MIGRATOR_ON_ETHEREUM);
address migratorL2 = resolver.getAddress(CONTRACT_DEBT_MIGRATOR_ON_OPTIMISM);
require(msg.sender == migratorL1 || msg.sender == migratorL2, "only trusted migrators");
require(migratorL1 == address(0) || migratorL2 == address(0), "one migrator must be 0x0");
_;
}
function _onlySynthRedeemer() internal view {
require(msg.sender == address(synthRedeemer()), "Only SynthRedeemer");
}
modifier onlySynthRedeemer() {
_onlySynthRedeemer();
_;
}
/* ========== EVENTS ========== */
event SynthAdded(bytes32 currencyKey, address synth);
event SynthRemoved(bytes32 currencyKey, address synth);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
// https://docs.synthetix.io/contracts/source/interfaces/istakingrewards
interface IStakingRewards {
// Views
function balanceOf(address account) external view returns (uint256);
function earned(address account) external view returns (uint256);
function getRewardForDuration() external view returns (uint256);
function lastTimeRewardApplicable() external view returns (uint256);
function rewardPerToken() external view returns (uint256);
function rewardsDistribution() external view returns (address);
function rewardsToken() external view returns (address);
function totalSupply() external view returns (uint256);
// Mutative
function exit() external;
function getReward() external;
function stake(uint256 amount) external;
function withdraw(uint256 amount) external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
// https://docs.synthetix.io/contracts/source/interfaces/isupplyschedule
interface ISupplySchedule {
// Views
function mintableSupply() external view returns (uint);
function isMintable() external view returns (bool);
function minterReward() external view returns (uint);
// Mutative functions
function recordMintEvent(uint supplyMinted) external returns (uint);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
// https://docs.synthetix.io/contracts/source/interfaces/isynth
interface ISynth {
// Views
function currencyKey() external view returns (bytes32);
function transferableSynths(address account) external view returns (uint);
// Mutative functions
function transferAndSettle(address to, uint value) external returns (bool);
function transferFromAndSettle(
address from,
address to,
uint value
) external returns (bool);
// Restricted: used internally to Synthetix
function burn(address account, uint amount) external;
function issue(address account, uint amount) external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
import "./ISynth.sol";
import "./IVirtualSynth.sol";
// https://docs.synthetix.io/contracts/source/interfaces/isynthetix
interface ISynthetix {
// Views
function anySynthOrSNXRateIsInvalid() external view returns (bool anyRateInvalid);
function availableCurrencyKeys() external view returns (bytes32[] memory);
function availableSynthCount() external view returns (uint);
function availableSynths(uint index) external view returns (ISynth);
function collateral(address account) external view returns (uint);
function collateralisationRatio(address issuer) external view returns (uint);
function debtBalanceOf(address issuer, bytes32 currencyKey) external view returns (uint);
function isWaitingPeriod(bytes32 currencyKey) external view returns (bool);
function maxIssuableSynths(address issuer) external view returns (uint maxIssuable);
function remainingIssuableSynths(address issuer)
external
view
returns (
uint maxIssuable,
uint alreadyIssued,
uint totalSystemDebt
);
function synths(bytes32 currencyKey) external view returns (ISynth);
function synthsByAddress(address synthAddress) external view returns (bytes32);
function totalIssuedSynths(bytes32 currencyKey) external view returns (uint);
function totalIssuedSynthsExcludeOtherCollateral(bytes32 currencyKey) external view returns (uint);
function transferableSynthetix(address account) external view returns (uint transferable);
function getFirstNonZeroEscrowIndex(address account) external view returns (uint);
// Mutative Functions
function burnSynths(uint amount) external;
function burnSynthsOnBehalf(address burnForAddress, uint amount) external;
function burnSynthsToTarget() external;
function burnSynthsToTargetOnBehalf(address burnForAddress) external;
function exchange(
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey
) external returns (uint amountReceived);
function exchangeOnBehalf(
address exchangeForAddress,
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey
) external returns (uint amountReceived);
function exchangeWithTracking(
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey,
address rewardAddress,
bytes32 trackingCode
) external returns (uint amountReceived);
function exchangeWithTrackingForInitiator(
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey,
address rewardAddress,
bytes32 trackingCode
) external returns (uint amountReceived);
function exchangeOnBehalfWithTracking(
address exchangeForAddress,
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey,
address rewardAddress,
bytes32 trackingCode
) external returns (uint amountReceived);
function exchangeWithVirtual(
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey,
bytes32 trackingCode
) external returns (uint amountReceived, IVirtualSynth vSynth);
function exchangeAtomically(
bytes32 sourceCurrencyKey,
uint sourceAmount,
bytes32 destinationCurrencyKey,
bytes32 trackingCode,
uint minAmount
) external returns (uint amountReceived);
function issueMaxSynths() external;
function issueMaxSynthsOnBehalf(address issueForAddress) external;
function issueSynths(uint amount) external;
function issueSynthsOnBehalf(address issueForAddress, uint amount) external;
function mint() external returns (bool);
function settle(bytes32 currencyKey)
external
returns (
uint reclaimed,
uint refunded,
uint numEntries
);
// Liquidations
function liquidateDelinquentAccount(address account) external returns (bool);
function liquidateDelinquentAccountEscrowIndex(address account, uint escrowStartIndex) external returns (bool);
function liquidateSelf() external returns (bool);
// Restricted Functions
function mintSecondary(address account, uint amount) external;
function mintSecondaryRewards(uint amount) external;
function burnSecondary(address account, uint amount) external;
function revokeAllEscrow(address account) external;
function migrateAccountBalances(address account) external returns (uint totalEscrowRevoked, uint totalLiquidBalance);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
interface ISynthetixBridgeEscrow {
function approveBridge(
address _token,
address _bridge,
uint256 _amount
) external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
pragma experimental ABIEncoderV2;
import "./IRewardEscrowV2.sol";
interface ISynthetixBridgeToBase {
// invoked by the xDomain messenger on L2
function finalizeEscrowMigration(
address account,
uint256 escrowedAmount,
VestingEntries.VestingEntry[] calldata vestingEntries
) external;
// invoked by the xDomain messenger on L2
function finalizeRewardDeposit(address from, uint amount) external;
function finalizeFeePeriodClose(uint snxBackedDebt, uint debtSharesSupply) external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
pragma experimental ABIEncoderV2;
interface ISynthetixBridgeToOptimism {
function closeFeePeriod(uint snxBackedDebt, uint debtSharesSupply) external;
function migrateEscrow(uint256[][] calldata entryIDs) external;
function depositTo(address to, uint amount) external;
function depositReward(uint amount) external;
function depositAndMigrateEscrow(uint256 depositAmount, uint256[][] calldata entryIDs) external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
// https://docs.synthetix.io/contracts/source/interfaces/isynthetixdebtshare
interface ISynthetixDebtShare {
// Views
function currentPeriodId() external view returns (uint128);
function allowance(address account, address spender) external view returns (uint);
function balanceOf(address account) external view returns (uint);
function balanceOfOnPeriod(address account, uint periodId) external view returns (uint);
function totalSupply() external view returns (uint);
function sharePercent(address account) external view returns (uint);
function sharePercentOnPeriod(address account, uint periodId) external view returns (uint);
// Mutative functions
function takeSnapshot(uint128 id) external;
function mintShare(address account, uint256 amount) external;
function burnShare(address account, uint256 amount) external;
function approve(address, uint256) external pure returns (bool);
function transfer(address to, uint256 amount) external pure returns (bool);
function transferFrom(
address from,
address to,
uint256 amount
) external returns (bool);
function addAuthorizedBroker(address target) external;
function removeAuthorizedBroker(address target) external;
function addAuthorizedToSnapshot(address target) external;
function removeAuthorizedToSnapshot(address target) external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
// https://docs.synthetix.io/contracts/source/interfaces/isynthetixstate
interface ISynthetixState {
// Views
function debtLedger(uint index) external view returns (uint);
function issuanceData(address account) external view returns (uint initialDebtOwnership, uint debtEntryIndex);
function debtLedgerLength() external view returns (uint);
function hasIssued(address account) external view returns (bool);
function lastDebtLedgerEntry() external view returns (uint);
// Mutative functions
function incrementTotalIssuerCount() external;
function decrementTotalIssuerCount() external;
function setCurrentIssuanceData(address account, uint initialDebtOwnership) external;
function appendDebtLedgerValue(uint value) external;
function clearIssuanceData(address account) external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
import "./IERC20.sol";
interface ISynthRedeemer {
// Rate of redemption - 0 for none
function redemptions(address synthProxy) external view returns (uint redeemRate);
// sUSD balance of deprecated token holder
function balanceOf(IERC20 synthProxy, address account) external view returns (uint balanceOfInsUSD);
// Full sUSD supply of token
function totalSupply(IERC20 synthProxy) external view returns (uint totalSupplyInsUSD);
function redeem(IERC20 synthProxy) external;
function redeemAll(IERC20[] calldata synthProxies) external;
function redeemPartial(IERC20 synthProxy, uint amountOfSynth) external;
// Restricted to Issuer
function deprecate(IERC20 synthProxy, uint rateToRedeem) external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
// https://docs.synthetix.io/contracts/source/interfaces/isystemsettings
interface ISystemSettings {
// Views
function waitingPeriodSecs() external view returns (uint);
function priceDeviationThresholdFactor() external view returns (uint);
function issuanceRatio() external view returns (uint);
function feePeriodDuration() external view returns (uint);
function targetThreshold() external view returns (uint);
function liquidationDelay() external view returns (uint);
function liquidationRatio() external view returns (uint);
function liquidationEscrowDuration() external view returns (uint);
function liquidationPenalty() external view returns (uint);
function snxLiquidationPenalty() external view returns (uint);
function selfLiquidationPenalty() external view returns (uint);
function flagReward() external view returns (uint);
function liquidateReward() external view returns (uint);
function rateStalePeriod() external view returns (uint);
function exchangeFeeRate(bytes32 currencyKey) external view returns (uint);
function minimumStakeTime() external view returns (uint);
function debtSnapshotStaleTime() external view returns (uint);
function aggregatorWarningFlags() external view returns (address);
function tradingRewardsEnabled() external view returns (bool);
function wrapperMaxTokenAmount(address wrapper) external view returns (uint);
function wrapperMintFeeRate(address wrapper) external view returns (int);
function wrapperBurnFeeRate(address wrapper) external view returns (int);
function etherWrapperMaxETH() external view returns (uint);
function etherWrapperBurnFeeRate() external view returns (uint);
function etherWrapperMintFeeRate() external view returns (uint);
function interactionDelay(address collateral) external view returns (uint);
function atomicMaxVolumePerBlock() external view returns (uint);
function atomicTwapWindow() external view returns (uint);
function atomicEquivalentForDexPricing(bytes32 currencyKey) external view returns (address);
function atomicExchangeFeeRate(bytes32 currencyKey) external view returns (uint);
function atomicVolatilityConsiderationWindow(bytes32 currencyKey) external view returns (uint);
function atomicVolatilityUpdateThreshold(bytes32 currencyKey) external view returns (uint);
function pureChainlinkPriceForAtomicSwapsEnabled(bytes32 currencyKey) external view returns (bool);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
// https://docs.synthetix.io/contracts/source/interfaces/isystemstatus
interface ISystemStatus {
struct Status {
bool canSuspend;
bool canResume;
}
struct Suspension {
bool suspended;
// reason is an integer code,
// 0 => no reason, 1 => upgrading, 2+ => defined by system usage
uint248 reason;
}
// Views
function accessControl(bytes32 section, address account) external view returns (bool canSuspend, bool canResume);
function requireSystemActive() external view;
function systemSuspended() external view returns (bool);
function requireIssuanceActive() external view;
function requireExchangeActive() external view;
function requireFuturesActive() external view;
function requireFuturesMarketActive(bytes32 marketKey) external view;
function requireExchangeBetweenSynthsAllowed(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey) external view;
function requireSynthActive(bytes32 currencyKey) external view;
function synthSuspended(bytes32 currencyKey) external view returns (bool);
function requireSynthsActive(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey) external view;
function systemSuspension() external view returns (bool suspended, uint248 reason);
function issuanceSuspension() external view returns (bool suspended, uint248 reason);
function exchangeSuspension() external view returns (bool suspended, uint248 reason);
function futuresSuspension() external view returns (bool suspended, uint248 reason);
function synthExchangeSuspension(bytes32 currencyKey) external view returns (bool suspended, uint248 reason);
function synthSuspension(bytes32 currencyKey) external view returns (bool suspended, uint248 reason);
function futuresMarketSuspension(bytes32 marketKey) external view returns (bool suspended, uint248 reason);
function getSynthExchangeSuspensions(bytes32[] calldata synths)
external
view
returns (bool[] memory exchangeSuspensions, uint256[] memory reasons);
function getSynthSuspensions(bytes32[] calldata synths)
external
view
returns (bool[] memory suspensions, uint256[] memory reasons);
function getFuturesMarketSuspensions(bytes32[] calldata marketKeys)
external
view
returns (bool[] memory suspensions, uint256[] memory reasons);
// Restricted functions
function suspendIssuance(uint256 reason) external;
function suspendSynth(bytes32 currencyKey, uint256 reason) external;
function suspendFuturesMarket(bytes32 marketKey, uint256 reason) external;
function updateAccessControl(
bytes32 section,
address account,
bool canSuspend,
bool canResume
) external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
// https://docs.synthetix.io/contracts/source/interfaces/itradingrewards
interface ITradingRewards {
/* ========== VIEWS ========== */
function getAvailableRewards() external view returns (uint);
function getUnassignedRewards() external view returns (uint);
function getRewardsToken() external view returns (address);
function getPeriodController() external view returns (address);
function getCurrentPeriod() external view returns (uint);
function getPeriodIsClaimable(uint periodID) external view returns (bool);
function getPeriodIsFinalized(uint periodID) external view returns (bool);
function getPeriodRecordedFees(uint periodID) external view returns (uint);
function getPeriodTotalRewards(uint periodID) external view returns (uint);
function getPeriodAvailableRewards(uint periodID) external view returns (uint);
function getUnaccountedFeesForAccountForPeriod(address account, uint periodID) external view returns (uint);
function getAvailableRewardsForAccountForPeriod(address account, uint periodID) external view returns (uint);
function getAvailableRewardsForAccountForPeriods(address account, uint[] calldata periodIDs)
external
view
returns (uint totalRewards);
/* ========== MUTATIVE FUNCTIONS ========== */
function claimRewardsForPeriod(uint periodID) external;
function claimRewardsForPeriods(uint[] calldata periodIDs) external;
/* ========== RESTRICTED FUNCTIONS ========== */
function recordExchangeFeeForAccount(uint usdFeeAmount, address account) external;
function closeCurrentPeriodWithRewards(uint rewards) external;
function recoverTokens(address tokenAddress, address recoverAddress) external;
function recoverUnassignedRewardTokens(address recoverAddress) external;
function recoverAssignedRewardTokensAndDestroyPeriod(address recoverAddress, uint periodID) external;
function setPeriodController(address newPeriodController) external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
import "./ISynth.sol";
interface IVirtualSynth {
// Views
function balanceOfUnderlying(address account) external view returns (uint);
function rate() external view returns (uint);
function readyToSettle() external view returns (bool);
function secsLeftInWaitingPeriod() external view returns (uint);
function settled() external view returns (bool);
function synth() external view returns (ISynth);
// Mutative functions
function settle(address account) external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
interface IWETH {
// ERC20 Optional Views
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
// Views
function totalSupply() external view returns (uint);
function balanceOf(address owner) external view returns (uint);
function allowance(address owner, address spender) external view returns (uint);
// Mutative functions
function transfer(address to, uint value) external returns (bool);
function approve(address spender, uint value) external returns (bool);
function transferFrom(
address from,
address to,
uint value
) external returns (bool);
// WETH-specific functions.
function deposit() external payable;
function withdraw(uint amount) external;
// Events
event Transfer(address indexed from, address indexed to, uint value);
event Approval(address indexed owner, address indexed spender, uint value);
event Deposit(address indexed to, uint amount);
event Withdrawal(address indexed to, uint amount);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
import "./IERC20.sol";
// https://docs.synthetix.io/contracts/source/interfaces/iwrapper
interface IWrapper {
function mint(uint amount) external;
function burn(uint amount) external;
function capacity() external view returns (uint);
function totalIssuedSynths() external view returns (uint);
function calculateMintFee(uint amount) external view returns (uint, bool);
function calculateBurnFee(uint amount) external view returns (uint, bool);
function maxTokenAmount() external view returns (uint256);
function mintFeeRate() external view returns (int256);
function burnFeeRate() external view returns (int256);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity >=0.4.24;
import "./IERC20.sol";
// https://docs.synthetix.io/contracts/source/interfaces/iwrapperfactory
interface IWrapperFactory {
function isWrapper(address possibleWrapper) external view returns (bool);
function createWrapper(
IERC20 token,
bytes32 currencyKey,
bytes32 synthContractName
) external returns (address);
function distributeFees() external;
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
contract LegacyOwned {
address public owner;
address public nominatedOwner;
constructor(address _owner) public {
owner = _owner;
}
function nominateOwner(address _owner) external onlyOwner {
nominatedOwner = _owner;
emit OwnerNominated(_owner);
}
function acceptOwnership() external {
require(msg.sender == nominatedOwner);
emit OwnerChanged(owner, nominatedOwner);
owner = nominatedOwner;
nominatedOwner = address(0);
}
modifier onlyOwner {
require(msg.sender == owner);
_;
}
event OwnerNominated(address newOwner);
event OwnerChanged(address oldOwner, address newOwner);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
import "./LegacyOwned.sol";
contract LegacyTokenState is LegacyOwned {
// the address of the contract that can modify balances and allowances
// this can only be changed by the owner of this contract
address public associatedContract;
// ERC20 fields.
mapping(address => uint) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
constructor(address _owner, address _associatedContract) public LegacyOwned(_owner) {
associatedContract = _associatedContract;
emit AssociatedContractUpdated(_associatedContract);
}
/* ========== SETTERS ========== */
// Change the associated contract to a new address
function setAssociatedContract(address _associatedContract) external onlyOwner {
associatedContract = _associatedContract;
emit AssociatedContractUpdated(_associatedContract);
}
function setAllowance(
address tokenOwner,
address spender,
uint value
) external onlyAssociatedContract {
allowance[tokenOwner][spender] = value;
}
function setBalanceOf(address account, uint value) external onlyAssociatedContract {
balanceOf[account] = value;
}
/* ========== MODIFIERS ========== */
modifier onlyAssociatedContract {
require(msg.sender == associatedContract);
_;
}
/* ========== EVENTS ========== */
event AssociatedContractUpdated(address _associatedContract);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
// https://docs.synthetix.io/contracts/source/contracts/limitedsetup
contract LimitedSetup {
uint public setupExpiryTime;
/**
* @dev LimitedSetup Constructor.
* @param setupDuration The time the setup period will last for.
*/
constructor(uint setupDuration) internal {
setupExpiryTime = now + setupDuration;
}
modifier onlyDuringSetup {
require(now < setupExpiryTime, "Can only perform this action during setup");
_;
}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
// Inheritance
import "./Owned.sol";
import "./MixinResolver.sol";
import "./MixinSystemSettings.sol";
import "./ILiquidator.sol";
// Libraries
import "./SafeDecimalMath.sol";
// Internal references
import "./IERC20.sol";
import "./ISynthetix.sol";
import "./IExchangeRates.sol";
import "./IIssuer.sol";
import "./ISystemStatus.sol";
import "./IHasBalance.sol";
/// @title Upgrade Liquidation Mechanism V2 (SIP-148)
/// @notice This contract is a modification to the existing liquidation mechanism defined in SIP-15
contract Liquidator is Owned, MixinSystemSettings, ILiquidator {
using SafeMath for uint;
using SafeDecimalMath for uint;
struct LiquidationEntry {
uint deadline;
address caller;
}
/* ========== ADDRESS RESOLVER CONFIGURATION ========== */
bytes32 private constant CONTRACT_SYSTEMSTATUS = "SystemStatus";
bytes32 private constant CONTRACT_SYNTHETIX = "Synthetix";
bytes32 private constant CONTRACT_ISSUER = "Issuer";
bytes32 private constant CONTRACT_EXRATES = "ExchangeRates";
bytes32 private constant CONTRACT_SYNTHETIXESCROW = "SynthetixEscrow";
bytes32 private constant CONTRACT_V3_LEGACYMARKET = "LegacyMarket";
/* ========== CONSTANTS ========== */
bytes32 public constant CONTRACT_NAME = "Liquidator";
// Storage keys
bytes32 public constant LIQUIDATION_DEADLINE = "LiquidationDeadline";
bytes32 public constant LIQUIDATION_CALLER = "LiquidationCaller";
constructor(address _owner, address _resolver) public Owned(_owner) MixinSystemSettings(_resolver) {}
/* ========== VIEWS ========== */
function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
bytes32[] memory existingAddresses = MixinSystemSettings.resolverAddressesRequired();
bytes32[] memory newAddresses = new bytes32[](4);
newAddresses[0] = CONTRACT_SYSTEMSTATUS;
newAddresses[1] = CONTRACT_SYNTHETIX;
newAddresses[2] = CONTRACT_ISSUER;
newAddresses[3] = CONTRACT_EXRATES;
addresses = combineArrays(existingAddresses, newAddresses);
}
function synthetix() internal view returns (ISynthetix) {
return ISynthetix(requireAndGetAddress(CONTRACT_SYNTHETIX));
}
function systemStatus() internal view returns (ISystemStatus) {
return ISystemStatus(requireAndGetAddress(CONTRACT_SYSTEMSTATUS));
}
function issuer() internal view returns (IIssuer) {
return IIssuer(requireAndGetAddress(CONTRACT_ISSUER));
}
function exchangeRates() internal view returns (IExchangeRates) {
return IExchangeRates(requireAndGetAddress(CONTRACT_EXRATES));
}
function issuanceRatio() external view returns (uint) {
return getIssuanceRatio();
}
function liquidationDelay() external view returns (uint) {
return getLiquidationDelay();
}
function liquidationRatio() external view returns (uint) {
return getLiquidationRatio();
}
function liquidationEscrowDuration() external view returns (uint) {
return getLiquidationEscrowDuration();
}
function liquidationPenalty() external view returns (uint) {
// SIP-251: use getSnxLiquidationPenalty instead of getLiquidationPenalty
// which is used for loans / shorts (collateral contracts).
// Keeping the view name because it makes sense in the context of this contract.
return getSnxLiquidationPenalty();
}
function selfLiquidationPenalty() external view returns (uint) {
return getSelfLiquidationPenalty();
}
function liquidateReward() external view returns (uint) {
return getLiquidateReward();
}
function flagReward() external view returns (uint) {
return getFlagReward();
}
function liquidationCollateralRatio() external view returns (uint) {
return SafeDecimalMath.unit().divideDecimalRound(getLiquidationRatio());
}
function getLiquidationDeadlineForAccount(address account) external view returns (uint) {
LiquidationEntry memory liquidation = _getLiquidationEntryForAccount(account);
return liquidation.deadline;
}
function getLiquidationCallerForAccount(address account) external view returns (address) {
LiquidationEntry memory liquidation = _getLiquidationEntryForAccount(account);
return liquidation.caller;
}
/// @notice Determines if an account is eligible for forced or self liquidation
/// @dev An account is eligible to self liquidate if its c-ratio is below the target c-ratio
/// @dev An account with no SNX collateral will not be open for liquidation since the ratio is 0
function isLiquidationOpen(address account, bool isSelfLiquidation) external view returns (bool) {
uint accountCollateralisationRatio = synthetix().collateralisationRatio(account);
// Not open for liquidation if collateral ratio is less than or equal to target issuance ratio
if (accountCollateralisationRatio <= getIssuanceRatio()) {
return false;
}
if (!isSelfLiquidation) {
LiquidationEntry memory liquidation = _getLiquidationEntryForAccount(account);
// Open for liquidation if the deadline has passed and the user has enough SNX collateral.
if (_deadlinePassed(liquidation.deadline) && _hasEnoughSNXForRewards(account)) {
return true;
}
return false;
} else {
// Not open for self-liquidation when the account's collateral value is less than debt issued + forced penalty
uint unit = SafeDecimalMath.unit();
if (accountCollateralisationRatio > (unit.divideDecimal(unit.add(getSnxLiquidationPenalty())))) {
return false;
}
}
return true;
}
/// View for calculating the amounts of collateral (liquid and escrow that will be liquidated), and debt that will
/// be removed.
/// @param account The account to be liquidated
/// @param isSelfLiquidation boolean to determine if this is a forced or self-invoked liquidation
/// @return totalRedeemed the total amount of collateral (SNX) to redeem (liquid and escrow)
/// @return debtToRemove the amount of debt (sUSD) to burn in order to fix the account's c-ratio
/// @return escrowToLiquidate the amount of escrow SNX that will be revoked during liquidation
/// @return initialDebtBalance the amount of initial (sUSD) debt the account has
function liquidationAmounts(address account, bool isSelfLiquidation)
external
view
returns (
uint totalRedeemed,
uint debtToRemove,
uint escrowToLiquidate,
uint initialDebtBalance
)
{
// return zeroes otherwise calculateAmountToFixCollateral reverts with unhelpful underflow error
if (!this.isLiquidationOpen(account, isSelfLiquidation)) {
return (0, 0, 0, issuer().debtBalanceOf(account, "sUSD"));
}
return issuer().liquidationAmounts(account, isSelfLiquidation);
}
function isLiquidationDeadlinePassed(address account) external view returns (bool) {
LiquidationEntry memory liquidation = _getLiquidationEntryForAccount(account);
return _deadlinePassed(liquidation.deadline);
}
function _deadlinePassed(uint deadline) internal view returns (bool) {
// check deadline is set > 0
// check now > deadline
return deadline > 0 && now > deadline;
}
/// @notice Checks if an account has enough SNX balance to be considered open for forced liquidation.
function _hasEnoughSNXForRewards(address account) internal view returns (bool) {
uint balance = issuer().collateral(account);
return balance >= (getLiquidateReward().add(getFlagReward()));
}
/**
* r = target issuance ratio
* D = debt value
* V = collateral value
* P = liquidation penalty
* S = debt amount to redeem
* Calculates amount of synths = (D - V * r) / (1 - (1 + P) * r)
*
* Derivation of the formula:
* Collateral "sold" with penalty: collateral-sold = S * (1 + P)
* After liquidation: new-debt = D - S, new-collateral = V - collateral-sold = V - S * (1 + P)
* Because we fixed the c-ratio, new-debt / new-collateral = c-ratio: (D - S) / (V - S * (1 + P)) = r
* After solving for S we get: S = (D - V * r) / (1 - (1 + P) * r)
* Note: this only returns the amount of debt to remove "assuming the penalty", the penalty still needs to be
* correctly applied when removing collateral.
*/
function calculateAmountToFixCollateral(
uint debtBalance,
uint collateral,
uint penalty
) external view returns (uint) {
uint ratio = getIssuanceRatio();
uint unit = SafeDecimalMath.unit();
uint dividend = debtBalance.sub(collateral.multiplyDecimal(ratio));
uint divisor = unit.sub(unit.add(penalty).multiplyDecimal(ratio));
return dividend.divideDecimal(divisor);
}
// get liquidationEntry for account
// returns deadline = 0 when not set
function _getLiquidationEntryForAccount(address account) internal view returns (LiquidationEntry memory _liquidation) {
_liquidation.deadline = flexibleStorage().getUIntValue(CONTRACT_NAME, _getKey(LIQUIDATION_DEADLINE, account));
// This is used to reward the caller for flagging an account for liquidation.
_liquidation.caller = flexibleStorage().getAddressValue(CONTRACT_NAME, _getKey(LIQUIDATION_CALLER, account));
}
function _getKey(bytes32 _scope, address _account) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(_scope, _account));
}
/* ========== MUTATIVE FUNCTIONS ========== */
// totalIssuedSynths checks synths for staleness
// check snx rate is not stale
function flagAccountForLiquidation(address account) external rateNotInvalid("SNX") {
systemStatus().requireSystemActive();
require(resolver.getAddress(CONTRACT_V3_LEGACYMARKET) == address(0), "Must liquidate using V3");
require(getLiquidationRatio() > 0, "Liquidation ratio not set");
require(getLiquidationDelay() > 0, "Liquidation delay not set");
LiquidationEntry memory liquidation = _getLiquidationEntryForAccount(account);
require(liquidation.deadline == 0, "Account already flagged for liquidation");
uint accountsCollateralisationRatio = synthetix().collateralisationRatio(account);
// if accounts issuance ratio is greater than or equal to liquidation ratio set liquidation entry
require(
accountsCollateralisationRatio >= getLiquidationRatio(),
"Account issuance ratio is less than liquidation ratio"
);
// if account doesn't have enough liquidatable collateral for rewards the liquidation transaction
// is not possible
require(_hasEnoughSNXForRewards(account), "not enough SNX for rewards");
uint deadline = now.add(getLiquidationDelay());
_storeLiquidationEntry(account, deadline, msg.sender);
emit AccountFlaggedForLiquidation(account, deadline);
}
/// @notice This function is called by the Issuer to remove an account's liquidation entry
/// @dev The Issuer must check if the account's c-ratio is fixed before removing
function removeAccountInLiquidation(address account) external onlyIssuer {
LiquidationEntry memory liquidation = _getLiquidationEntryForAccount(account);
if (liquidation.deadline > 0) {
_removeLiquidationEntry(account);
}
}
/// @notice External function to allow anyone to remove an account's liquidation entry
/// @dev This function checks if the account's c-ratio is OK and that the rate of SNX is not stale
function checkAndRemoveAccountInLiquidation(address account) external rateNotInvalid("SNX") {
systemStatus().requireSystemActive();
LiquidationEntry memory liquidation = _getLiquidationEntryForAccount(account);
require(liquidation.deadline > 0, "Account has no liquidation set");
uint accountsCollateralisationRatio = synthetix().collateralisationRatio(account);
// Remove from liquidator if accountsCollateralisationRatio is fixed (less than equal target issuance ratio)
if (accountsCollateralisationRatio <= getIssuanceRatio()) {
_removeLiquidationEntry(account);
}
}
function _storeLiquidationEntry(
address _account,
uint _deadline,
address _caller
) internal {
// record liquidation deadline and caller
flexibleStorage().setUIntValue(CONTRACT_NAME, _getKey(LIQUIDATION_DEADLINE, _account), _deadline);
flexibleStorage().setAddressValue(CONTRACT_NAME, _getKey(LIQUIDATION_CALLER, _account), _caller);
}
/// @notice Only delete the deadline value, keep caller for flag reward payout
function _removeLiquidationEntry(address _account) internal {
flexibleStorage().deleteUIntValue(CONTRACT_NAME, _getKey(LIQUIDATION_DEADLINE, _account));
emit AccountRemovedFromLiquidation(_account, now);
}
/* ========== MODIFIERS ========== */
modifier onlyIssuer() {
require(msg.sender == address(issuer()), "Liquidator: Only the Issuer contract can perform this action");
_;
}
modifier rateNotInvalid(bytes32 currencyKey) {
require(!exchangeRates().rateIsInvalid(currencyKey), "Rate invalid or not a synth");
_;
}
/* ========== EVENTS ========== */
event AccountFlaggedForLiquidation(address indexed account, uint deadline);
event AccountRemovedFromLiquidation(address indexed account, uint time);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
import "./SafeMath.sol";
import "./SafeERC20.sol";
import "./ReentrancyGuard.sol";
// Inheritance
import "./Owned.sol";
import "./MixinResolver.sol";
import "./MixinSystemSettings.sol";
import "./ILiquidatorRewards.sol";
// Libraries
import "./SafeDecimalMath.sol";
// Internal references
import "./IIssuer.sol";
import "./IRewardEscrowV2.sol";
import "./ISynthetixDebtShare.sol";
/// @title Liquidator Rewards (SIP-148)
/// @notice This contract holds SNX from liquidated positions.
/// @dev SNX stakers may claim their rewards based on their share of the debt pool.
contract LiquidatorRewards is ILiquidatorRewards, Owned, MixinSystemSettings, ReentrancyGuard {
using SafeMath for uint256;
using SafeDecimalMath for uint256;
using SafeERC20 for IERC20;
struct AccountRewardsEntry {
uint128 claimable;
uint128 entryAccumulatedRewards;
}
/* ========== STATE VARIABLES ========== */
uint256 public accumulatedRewardsPerShare;
mapping(address => AccountRewardsEntry) public entries;
mapping(address => bool) public initiated;
bytes32 public constant CONTRACT_NAME = "LiquidatorRewards";
/* ========== ADDRESS RESOLVER CONFIGURATION ========== */
bytes32 private constant CONTRACT_SYNTHETIXDEBTSHARE = "SynthetixDebtShare";
bytes32 private constant CONTRACT_ISSUER = "Issuer";
bytes32 private constant CONTRACT_REWARDESCROW_V2 = "RewardEscrowV2";
bytes32 private constant CONTRACT_SYNTHETIX = "Synthetix";
/* ========== CONSTRUCTOR ========== */
constructor(address _owner, address _resolver) public Owned(_owner) MixinSystemSettings(_resolver) {}
/* ========== VIEWS ========== */
function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
bytes32[] memory existingAddresses = MixinSystemSettings.resolverAddressesRequired();
bytes32[] memory newAddresses = new bytes32[](4);
newAddresses[0] = CONTRACT_SYNTHETIXDEBTSHARE;
newAddresses[1] = CONTRACT_ISSUER;
newAddresses[2] = CONTRACT_REWARDESCROW_V2;
newAddresses[3] = CONTRACT_SYNTHETIX;
return combineArrays(existingAddresses, newAddresses);
}
function synthetixDebtShare() internal view returns (ISynthetixDebtShare) {
return ISynthetixDebtShare(requireAndGetAddress(CONTRACT_SYNTHETIXDEBTSHARE));
}
function issuer() internal view returns (IIssuer) {
return IIssuer(requireAndGetAddress(CONTRACT_ISSUER));
}
function rewardEscrowV2() internal view returns (IRewardEscrowV2) {
return IRewardEscrowV2(requireAndGetAddress(CONTRACT_REWARDESCROW_V2));
}
function synthetix() internal view returns (IERC20) {
return IERC20(requireAndGetAddress(CONTRACT_SYNTHETIX));
}
function earned(address account) public view returns (uint256) {
AccountRewardsEntry memory entry = entries[account];
return
synthetixDebtShare()
.balanceOf(account)
.multiplyDecimal(accumulatedRewardsPerShare.sub(entry.entryAccumulatedRewards))
.add(entry.claimable);
}
/* ========== MUTATIVE FUNCTIONS ========== */
function getReward(address account) external nonReentrant {
updateEntry(account);
uint256 reward = entries[account].claimable;
if (reward > 0) {
entries[account].claimable = 0;
synthetix().approve(address(rewardEscrowV2()), reward);
rewardEscrowV2().createEscrowEntry(account, reward, getLiquidationEscrowDuration());
emit RewardPaid(account, reward);
}
}
// called every time a user's number of debt shares changes, or they claim rewards
// has no useful purpose if called outside of these cases
function updateEntry(address account) public {
// when user enters for the first time
if (!initiated[account]) {
entries[account].entryAccumulatedRewards = uint128(accumulatedRewardsPerShare);
initiated[account] = true;
} else {
entries[account] = AccountRewardsEntry(uint128(earned(account)), uint128(accumulatedRewardsPerShare));
}
}
/* ========== RESTRICTED FUNCTIONS ========== */
/// @notice This is called only after an account is liquidated and the SNX rewards are sent to this contract.
function notifyRewardAmount(uint256 reward) external onlySynthetix {
uint sharesSupply = synthetixDebtShare().totalSupply();
if (sharesSupply > 0) {
accumulatedRewardsPerShare = accumulatedRewardsPerShare.add(reward.divideDecimal(sharesSupply));
}
}
/* ========== MODIFIERS ========== */
modifier onlySynthetix {
bool isSynthetix = msg.sender == address(synthetix());
require(isSynthetix, "Synthetix only");
_;
}
/* ========== EVENTS ========== */
event RewardPaid(address indexed user, uint256 reward);
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
// Libraries
import "./SafeDecimalMath.sol";
// https://docs.synthetix.io/contracts/source/libraries/math
library Math {
using SafeMath for uint;
using SafeDecimalMath for uint;
/**
* @dev Uses "exponentiation by squaring" algorithm where cost is 0(logN)
* vs 0(N) for naive repeated multiplication.
* Calculates x^n with x as fixed-point and n as regular unsigned int.
* Calculates to 18 digits of precision with SafeDecimalMath.unit()
*/
function powDecimal(uint x, uint n) internal pure returns (uint) {
// https://mpark.github.io/programming/2014/08/18/exponentiation-by-squaring/
uint result = SafeDecimalMath.unit();
while (n > 0) {
if (n % 2 != 0) {
result = result.multiplyDecimal(x);
}
x = x.multiplyDecimal(x);
n /= 2;
}
return result;
}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
import "./BaseMigration.sol";
import "./PerpsV2MarketState.sol";
import "./PerpsV2ExchangeRate.sol";
import "./FuturesMarketManager.sol";
import "./PerpsV2MarketSettings.sol";
import "./SystemStatus.sol";
import "./ExchangeRates.sol";
interface ISynthetixNamedContract {
// solhint-disable func-name-mixedcase
function CONTRACT_NAME() external view returns (bytes32);
}
// solhint-disable contract-name-camelcase
contract Migration_AnkaaOptimismStep1 is BaseMigration {
// https://explorer.optimism.io/address/0x6d4a64C57612841c2C6745dB2a4E4db34F002D20;
address public constant OWNER = 0x6d4a64C57612841c2C6745dB2a4E4db34F002D20;
// ----------------------------
// EXISTING SYNTHETIX CONTRACTS
// ----------------------------
// https://explorer.optimism.io/address/0xd10cd91683301c8C15eDA40F59e73d1b0BcfECDD
PerpsV2MarketState public constant perpsv2marketstateethbtcperp_i =
PerpsV2MarketState(0xd10cd91683301c8C15eDA40F59e73d1b0BcfECDD);
// https://explorer.optimism.io/address/0x2C15259D4886e2C0946f9aB7a5E389c86b3c3b04
PerpsV2ExchangeRate public constant perpsv2exchangerate_i =
PerpsV2ExchangeRate(0x2C15259D4886e2C0946f9aB7a5E389c86b3c3b04);
// https://explorer.optimism.io/address/0xd30bdFd7e7a65fE109D5dE1D4e95F3B800FB7463
FuturesMarketManager public constant futuresmarketmanager_i =
FuturesMarketManager(0xd30bdFd7e7a65fE109D5dE1D4e95F3B800FB7463);
// https://explorer.optimism.io/address/0x649F44CAC3276557D03223Dbf6395Af65b11c11c
PerpsV2MarketSettings public constant perpsv2marketsettings_i =
PerpsV2MarketSettings(0x649F44CAC3276557D03223Dbf6395Af65b11c11c);
// https://explorer.optimism.io/address/0xE8c41bE1A167314ABAF2423b72Bf8da826943FFD
SystemStatus public constant systemstatus_i = SystemStatus(0xE8c41bE1A167314ABAF2423b72Bf8da826943FFD);
// https://explorer.optimism.io/address/0x04412b2aE241C602Be87Bc1114238d50d08398Fb
PerpsV2MarketState public constant perpsv2marketstateetcperp_i =
PerpsV2MarketState(0x04412b2aE241C602Be87Bc1114238d50d08398Fb);
// https://explorer.optimism.io/address/0xF8dBEf33111A37879f35EE15507769CA927cf9C0
PerpsV2MarketState public constant perpsv2marketstatecompperp_i =
PerpsV2MarketState(0xF8dBEf33111A37879f35EE15507769CA927cf9C0);
// https://explorer.optimism.io/address/0x3d869950817920Eda9fC9A633ab7F06B97444dd7
PerpsV2MarketState public constant perpsv2marketstatexmrperp_i =
PerpsV2MarketState(0x3d869950817920Eda9fC9A633ab7F06B97444dd7);
// https://explorer.optimism.io/address/0x913bd76F7E1572CC8278CeF2D6b06e2140ca9Ce2
ExchangeRates public constant exchangerates_i = ExchangeRates(0x913bd76F7E1572CC8278CeF2D6b06e2140ca9Ce2);
// ----------------------------------
// NEW CONTRACTS DEPLOYED TO BE ADDED
// ----------------------------------
constructor() public BaseMigration(OWNER) {}
function contractsRequiringOwnership() public pure returns (address[] memory contracts) {
contracts = new address[](9);
contracts[0] = address(perpsv2marketstateethbtcperp_i);
contracts[1] = address(perpsv2exchangerate_i);
contracts[2] = address(futuresmarketmanager_i);
contracts[3] = address(perpsv2marketsettings_i);
contracts[4] = address(systemstatus_i);
contracts[5] = address(perpsv2marketstateetcperp_i);
contracts[6] = address(perpsv2marketstatecompperp_i);
contracts[7] = address(perpsv2marketstatexmrperp_i);
contracts[8] = address(exchangerates_i);
}
function migrate() external onlyOwner {
// ACCEPT OWNERSHIP for all contracts that require ownership to make changes
acceptAll();
// MIGRATION
// Add migration contract permission to pause
systemstatus_i.updateAccessControl("Futures", address(this), true, false);
perpsv2marketstateethbtcperp_i.linkOrInitializeState();
perpsv2exchangerate_addAssociatedContracts_1();
futuresmarketmanager_addProxiedMarkets_2();
perpsv2marketsettings_i.setTakerFee("sETHBTCPERP", 300000000000000000);
perpsv2marketsettings_i.setMakerFee("sETHBTCPERP", 300000000000000000);
perpsv2marketsettings_i.setTakerFeeDelayedOrder("sETHBTCPERP", 300000000000000000);
perpsv2marketsettings_i.setMakerFeeDelayedOrder("sETHBTCPERP", 300000000000000000);
perpsv2marketsettings_i.setTakerFeeOffchainDelayedOrder("sETHBTCPERP", 500000000000000);
perpsv2marketsettings_i.setMakerFeeOffchainDelayedOrder("sETHBTCPERP", 100000000000000);
perpsv2marketsettings_i.setNextPriceConfirmWindow("sETHBTCPERP", 2);
perpsv2marketsettings_i.setDelayedOrderConfirmWindow("sETHBTCPERP", 120);
perpsv2marketsettings_i.setMinDelayTimeDelta("sETHBTCPERP", 60);
perpsv2marketsettings_i.setMaxDelayTimeDelta("sETHBTCPERP", 6000);
perpsv2marketsettings_i.setOffchainDelayedOrderMinAge("sETHBTCPERP", 2);
perpsv2marketsettings_i.setOffchainDelayedOrderMaxAge("sETHBTCPERP", 60);
perpsv2marketsettings_i.setMaxLeverage("sETHBTCPERP", 27500000000000000000);
perpsv2marketsettings_i.setMaxMarketValue("sETHBTCPERP", 50000000000000000000000000);
perpsv2marketsettings_i.setMaxFundingVelocity("sETHBTCPERP", 27000000000000000000);
perpsv2marketsettings_i.setSkewScale("sETHBTCPERP", 1700000000000000000000000000);
perpsv2marketsettings_i.setOffchainMarketKey("sETHBTCPERP", "ocETHBTCPERP");
perpsv2marketsettings_i.setOffchainPriceDivergence("sETHBTCPERP", 25000000000000000);
perpsv2marketsettings_i.setLiquidationPremiumMultiplier("sETHBTCPERP", 1562500000000000000);
perpsv2marketsettings_i.setMaxLiquidationDelta("sETHBTCPERP", 600000000000000);
perpsv2marketsettings_i.setLiquidationBufferRatio("sETHBTCPERP", 7500000000000000);
perpsv2marketsettings_i.setMaxPD("sETHBTCPERP", 1200000000000000);
// Ensure perpsV2 market is paused according to config;
systemstatus_i.suspendFuturesMarket("sETHBTCPERP", 80);
// Ensure perpsV2 market is paused according to config;
systemstatus_i.suspendFuturesMarket("ocETHBTCPERP", 80);
perpsv2marketstateetcperp_i.linkOrInitializeState();
perpsv2exchangerate_addAssociatedContracts_28();
futuresmarketmanager_addProxiedMarkets_29();
perpsv2marketsettings_i.setTakerFee("sETCPERP", 300000000000000000);
perpsv2marketsettings_i.setMakerFee("sETCPERP", 300000000000000000);
perpsv2marketsettings_i.setTakerFeeDelayedOrder("sETCPERP", 300000000000000000);
perpsv2marketsettings_i.setMakerFeeDelayedOrder("sETCPERP", 300000000000000000);
perpsv2marketsettings_i.setTakerFeeOffchainDelayedOrder("sETCPERP", 800000000000000);
perpsv2marketsettings_i.setMakerFeeOffchainDelayedOrder("sETCPERP", 200000000000000);
perpsv2marketsettings_i.setNextPriceConfirmWindow("sETCPERP", 2);
perpsv2marketsettings_i.setDelayedOrderConfirmWindow("sETCPERP", 120);
perpsv2marketsettings_i.setMinDelayTimeDelta("sETCPERP", 60);
perpsv2marketsettings_i.setMaxDelayTimeDelta("sETCPERP", 6000);
perpsv2marketsettings_i.setOffchainDelayedOrderMinAge("sETCPERP", 2);
perpsv2marketsettings_i.setOffchainDelayedOrderMaxAge("sETCPERP", 60);
perpsv2marketsettings_i.setMaxLeverage("sETCPERP", 27500000000000000000);
perpsv2marketsettings_i.setMaxMarketValue("sETCPERP", 55000000000000000000000);
perpsv2marketsettings_i.setMaxFundingVelocity("sETCPERP", 27000000000000000000);
perpsv2marketsettings_i.setSkewScale("sETCPERP", 4000000000000000000000000);
perpsv2marketsettings_i.setOffchainMarketKey("sETCPERP", "ocETCPERP");
perpsv2marketsettings_i.setOffchainPriceDivergence("sETCPERP", 25000000000000000);
perpsv2marketsettings_i.setLiquidationPremiumMultiplier("sETCPERP", 1562500000000000000);
perpsv2marketsettings_i.setMaxLiquidationDelta("sETCPERP", 1000000000000000);
perpsv2marketsettings_i.setLiquidationBufferRatio("sETCPERP", 10000000000000000);
perpsv2marketsettings_i.setMaxPD("sETCPERP", 2000000000000000);
// Ensure perpsV2 market is paused according to config;
systemstatus_i.suspendFuturesMarket("sETCPERP", 80);
// Ensure perpsV2 market is paused according to config;
systemstatus_i.suspendFuturesMarket("ocETCPERP", 80);
perpsv2marketstatecompperp_i.linkOrInitializeState();
perpsv2exchangerate_addAssociatedContracts_55();
futuresmarketmanager_addProxiedMarkets_56();
perpsv2marketsettings_i.setTakerFee("sCOMPPERP", 300000000000000000);
perpsv2marketsettings_i.setMakerFee("sCOMPPERP", 300000000000000000);
perpsv2marketsettings_i.setTakerFeeDelayedOrder("sCOMPPERP", 300000000000000000);
perpsv2marketsettings_i.setMakerFeeDelayedOrder("sCOMPPERP", 300000000000000000);
perpsv2marketsettings_i.setTakerFeeOffchainDelayedOrder("sCOMPPERP", 1000000000000000);
perpsv2marketsettings_i.setMakerFeeOffchainDelayedOrder("sCOMPPERP", 200000000000000);
perpsv2marketsettings_i.setNextPriceConfirmWindow("sCOMPPERP", 2);
perpsv2marketsettings_i.setDelayedOrderConfirmWindow("sCOMPPERP", 120);
perpsv2marketsettings_i.setMinDelayTimeDelta("sCOMPPERP", 60);
perpsv2marketsettings_i.setMaxDelayTimeDelta("sCOMPPERP", 6000);
perpsv2marketsettings_i.setOffchainDelayedOrderMinAge("sCOMPPERP", 2);
perpsv2marketsettings_i.setOffchainDelayedOrderMaxAge("sCOMPPERP", 60);
perpsv2marketsettings_i.setMaxLeverage("sCOMPPERP", 27500000000000000000);
perpsv2marketsettings_i.setMaxMarketValue("sCOMPPERP", 15000000000000000000000);
perpsv2marketsettings_i.setMaxFundingVelocity("sCOMPPERP", 27000000000000000000);
perpsv2marketsettings_i.setSkewScale("sCOMPPERP", 860000000000000000000000);
perpsv2marketsettings_i.setOffchainMarketKey("sCOMPPERP", "ocCOMPPERP");
perpsv2marketsettings_i.setOffchainPriceDivergence("sCOMPPERP", 25000000000000000);
perpsv2marketsettings_i.setLiquidationPremiumMultiplier("sCOMPPERP", 3000000000000000000);
perpsv2marketsettings_i.setMaxLiquidationDelta("sCOMPPERP", 1200000000000000);
perpsv2marketsettings_i.setLiquidationBufferRatio("sCOMPPERP", 15000000000000000);
perpsv2marketsettings_i.setMaxPD("sCOMPPERP", 2400000000000000);
// Ensure perpsV2 market is paused according to config;
systemstatus_i.suspendFuturesMarket("sCOMPPERP", 80);
// Ensure perpsV2 market is paused according to config;
systemstatus_i.suspendFuturesMarket("ocCOMPPERP", 80);
perpsv2marketstatexmrperp_i.linkOrInitializeState();
perpsv2exchangerate_addAssociatedContracts_82();
futuresmarketmanager_addProxiedMarkets_83();
perpsv2marketsettings_i.setTakerFee("sXMRPERP", 300000000000000000);
perpsv2marketsettings_i.setMakerFee("sXMRPERP", 300000000000000000);
perpsv2marketsettings_i.setTakerFeeDelayedOrder("sXMRPERP", 300000000000000000);
perpsv2marketsettings_i.setMakerFeeDelayedOrder("sXMRPERP", 300000000000000000);
perpsv2marketsettings_i.setTakerFeeOffchainDelayedOrder("sXMRPERP", 1000000000000000);
perpsv2marketsettings_i.setMakerFeeOffchainDelayedOrder("sXMRPERP", 200000000000000);
perpsv2marketsettings_i.setNextPriceConfirmWindow("sXMRPERP", 2);
perpsv2marketsettings_i.setDelayedOrderConfirmWindow("sXMRPERP", 120);
perpsv2marketsettings_i.setMinDelayTimeDelta("sXMRPERP", 60);
perpsv2marketsettings_i.setMaxDelayTimeDelta("sXMRPERP", 6000);
perpsv2marketsettings_i.setOffchainDelayedOrderMinAge("sXMRPERP", 2);
perpsv2marketsettings_i.setOffchainDelayedOrderMaxAge("sXMRPERP", 60);
perpsv2marketsettings_i.setMaxLeverage("sXMRPERP", 27500000000000000000);
perpsv2marketsettings_i.setMaxMarketValue("sXMRPERP", 5000000000000000000000);
perpsv2marketsettings_i.setMaxFundingVelocity("sXMRPERP", 27000000000000000000);
perpsv2marketsettings_i.setSkewScale("sXMRPERP", 255000000000000000000000);
perpsv2marketsettings_i.setOffchainMarketKey("sXMRPERP", "ocXMRPERP");
perpsv2marketsettings_i.setOffchainPriceDivergence("sXMRPERP", 25000000000000000);
perpsv2marketsettings_i.setLiquidationPremiumMultiplier("sXMRPERP", 3000000000000000000);
perpsv2marketsettings_i.setMaxLiquidationDelta("sXMRPERP", 1200000000000000);
perpsv2marketsettings_i.setLiquidationBufferRatio("sXMRPERP", 15000000000000000);
perpsv2marketsettings_i.setMaxPD("sXMRPERP", 2400000000000000);
// Ensure perpsV2 market is paused according to config;
systemstatus_i.suspendFuturesMarket("sXMRPERP", 80);
// Ensure perpsV2 market is paused according to config;
systemstatus_i.suspendFuturesMarket("ocXMRPERP", 80);
// Remove permission to migration contract
systemstatus_i.updateAccessControl("Futures", address(this), false, false);
// Ensure the ExchangeRates contract has the standalone feed for MAV;
exchangerates_i.addAggregator("MAV", 0x51E06250C8E46c8E5DE41ac8B917a47D706128C2);
// Ensure the ExchangeRates contract has the standalone feed for ETHBTC;
exchangerates_i.addAggregator("ETHBTC", 0xe4b9bcD7d0AA917f19019165EB89BdbbF36d2cBe);
// Ensure the ExchangeRates contract has the standalone feed for ETC;
exchangerates_i.addAggregator("ETC", 0xb7B9A39CC63f856b90B364911CC324dC46aC1770);
// Ensure the ExchangeRates contract has the standalone feed for COMP;
exchangerates_i.addAggregator("COMP", 0xe1011160d78a80E2eEBD60C228EEf7af4Dfcd4d7);
// Ensure the ExchangeRates contract has the standalone feed for MKR;
exchangerates_i.addAggregator("MKR", 0x607b417DF51e0E1ed3A12fDb7FC0e8307ED250F3);
// Ensure the ExchangeRates contract has the standalone feed for RPL;
exchangerates_i.addAggregator("RPL", 0xADE082c91A6AeCC86fC11704a830e933e1b382eA);
// Ensure the ExchangeRates contract has the standalone feed for YFI;
exchangerates_i.addAggregator("YFI", 0x5cdC797acCBf57EE2363Fed9701262Abc87a232e);
// Ensure the PerpsV2ExchangeRate contract has the off-chain feed Id for MAV;
perpsv2exchangerate_i.setOffchainPriceFeedId(
"MAV",
0x5b131ede5d017511cf5280b9ebf20708af299266a033752b64180c4201363b11
);
// Ensure the PerpsV2ExchangeRate contract has the off-chain feed Id for ETHBTC;
perpsv2exchangerate_i.setOffchainPriceFeedId(
"ETHBTC",
0xc96458d393fe9deb7a7d63a0ac41e2898a67a7750dbd166673279e06c868df0a
);
// Ensure the PerpsV2ExchangeRate contract has the off-chain feed Id for ETC;
perpsv2exchangerate_i.setOffchainPriceFeedId(
"ETC",
0x7f5cc8d963fc5b3d2ae41fe5685ada89fd4f14b435f8050f28c7fd409f40c2d8
);
// Ensure the PerpsV2ExchangeRate contract has the off-chain feed Id for COMP;
perpsv2exchangerate_i.setOffchainPriceFeedId(
"COMP",
0x4a8e42861cabc5ecb50996f92e7cfa2bce3fd0a2423b0c44c9b423fb2bd25478
);
// Ensure the PerpsV2ExchangeRate contract has the off-chain feed Id for YFI;
perpsv2exchangerate_i.setOffchainPriceFeedId(
"YFI",
0x425f4b198ab2504936886c1e93511bb6720fbcf2045a4f3c0723bb213846022f
);
// Ensure the PerpsV2ExchangeRate contract has the off-chain feed Id for MKR;
perpsv2exchangerate_i.setOffchainPriceFeedId(
"MKR",
0x9375299e31c0deb9c6bc378e6329aab44cb48ec655552a70d4b9050346a30378
);
// Ensure the PerpsV2ExchangeRate contract has the off-chain feed Id for RPL;
perpsv2exchangerate_i.setOffchainPriceFeedId(
"RPL",
0x24f94ac0fd8638e3fc41aab2e4df933e63f763351b640bf336a6ec70651c4503
);
// Ensure the PerpsV2ExchangeRate contract has the off-chain feed Id for XMR;
perpsv2exchangerate_i.setOffchainPriceFeedId(
"XMR",
0x46b8cc9347f04391764a0361e0b17c3ba394b001e7c304f7650f6376e37c321d
);
// NOMINATE OWNERSHIP back to owner for aforementioned contracts
nominateAll();
}
function acceptAll() internal {
address[] memory contracts = contractsRequiringOwnership();
for (uint i = 0; i < contracts.length; i++) {
Owned(contracts[i]).acceptOwnership();
}
}
function nominateAll() internal {
address[] memory contracts = contractsRequiringOwnership();
for (uint i = 0; i < contracts.length; i++) {
returnOwnership(contracts[i]);
}
}
function perpsv2exchangerate_addAssociatedContracts_1() internal {
address[] memory perpsv2exchangerate_addAssociatedContracts_associatedContracts_1_0 = new address[](2);
perpsv2exchangerate_addAssociatedContracts_associatedContracts_1_0[0] = address(
0x978D4b5438D3E4EDf4f03682e5A53b48E56604c5
);
perpsv2exchangerate_addAssociatedContracts_associatedContracts_1_0[1] = address(
0xb16a8B06318C78c274f3BBc5CC5C9191B0d0c1A3
);
perpsv2exchangerate_i.addAssociatedContracts(perpsv2exchangerate_addAssociatedContracts_associatedContracts_1_0);
}
function futuresmarketmanager_addProxiedMarkets_2() internal {
address[] memory futuresmarketmanager_addProxiedMarkets_marketsToAdd_2_0 = new address[](1);
futuresmarketmanager_addProxiedMarkets_marketsToAdd_2_0[0] = address(0xD5FcCd43205CEF11FbaF9b38dF15ADbe1B186869);
futuresmarketmanager_i.addProxiedMarkets(futuresmarketmanager_addProxiedMarkets_marketsToAdd_2_0);
}
function perpsv2exchangerate_addAssociatedContracts_28() internal {
address[] memory perpsv2exchangerate_addAssociatedContracts_associatedContracts_28_0 = new address[](2);
perpsv2exchangerate_addAssociatedContracts_associatedContracts_28_0[0] = address(
0xAD35498D97f3b1a0B99de42da7Ad81c91156BA77
);
perpsv2exchangerate_addAssociatedContracts_associatedContracts_28_0[1] = address(
0x98d601E04527a0acBB603BaD845D9b7B8840de1c
);
perpsv2exchangerate_i.addAssociatedContracts(perpsv2exchangerate_addAssociatedContracts_associatedContracts_28_0);
}
function futuresmarketmanager_addProxiedMarkets_29() internal {
address[] memory futuresmarketmanager_addProxiedMarkets_marketsToAdd_29_0 = new address[](1);
futuresmarketmanager_addProxiedMarkets_marketsToAdd_29_0[0] = address(0x4bF3C1Af0FaA689e3A808e6Ad7a8d89d07BB9EC7);
futuresmarketmanager_i.addProxiedMarkets(futuresmarketmanager_addProxiedMarkets_marketsToAdd_29_0);
}
function perpsv2exchangerate_addAssociatedContracts_55() internal {
address[] memory perpsv2exchangerate_addAssociatedContracts_associatedContracts_55_0 = new address[](2);
perpsv2exchangerate_addAssociatedContracts_associatedContracts_55_0[0] = address(
0x6172289961007908442a0437891DcD966F368563
);
perpsv2exchangerate_addAssociatedContracts_associatedContracts_55_0[1] = address(
0x9f3be6Be18E8D0613f87c86A0b1875B74f404A11
);
perpsv2exchangerate_i.addAssociatedContracts(perpsv2exchangerate_addAssociatedContracts_associatedContracts_55_0);
}
function futuresmarketmanager_addProxiedMarkets_56() internal {
address[] memory futuresmarketmanager_addProxiedMarkets_marketsToAdd_56_0 = new address[](1);
futuresmarketmanager_addProxiedMarkets_marketsToAdd_56_0[0] = address(0xb7059Ed9950f2D9fDc0155fC0D79e63d4441e806);
futuresmarketmanager_i.addProxiedMarkets(futuresmarketmanager_addProxiedMarkets_marketsToAdd_56_0);
}
function perpsv2exchangerate_addAssociatedContracts_82() internal {
address[] memory perpsv2exchangerate_addAssociatedContracts_associatedContracts_82_0 = new address[](2);
perpsv2exchangerate_addAssociatedContracts_associatedContracts_82_0[0] = address(
0x239847700D9134cEEAEC306DAA40b569CEe1D5a0
);
perpsv2exchangerate_addAssociatedContracts_associatedContracts_82_0[1] = address(
0x926b1148DaFe298ff7Fdc2d01Ae1bC3Fa3b4FAE4
);
perpsv2exchangerate_i.addAssociatedContracts(perpsv2exchangerate_addAssociatedContracts_associatedContracts_82_0);
}
function futuresmarketmanager_addProxiedMarkets_83() internal {
address[] memory futuresmarketmanager_addProxiedMarkets_marketsToAdd_83_0 = new address[](1);
futuresmarketmanager_addProxiedMarkets_marketsToAdd_83_0[0] = address(0x2ea06E73083f1b3314Fa090eaE4a5F70eb058F2e);
futuresmarketmanager_i.addProxiedMarkets(futuresmarketmanager_addProxiedMarkets_marketsToAdd_83_0);
}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
import "./BaseMigration.sol";
import "./PerpsV2MarketState.sol";
import "./PerpsV2ExchangeRate.sol";
import "./FuturesMarketManager.sol";
import "./PerpsV2MarketSettings.sol";
import "./SystemStatus.sol";
interface ISynthetixNamedContract {
// solhint-disable func-name-mixedcase
function CONTRACT_NAME() external view returns (bytes32);
}
// solhint-disable contract-name-camelcase
contract Migration_AnkaaOptimismStep2 is BaseMigration {
// https://explorer.optimism.io/address/0x6d4a64C57612841c2C6745dB2a4E4db34F002D20;
address public constant OWNER = 0x6d4a64C57612841c2C6745dB2a4E4db34F002D20;
// ----------------------------
// EXISTING SYNTHETIX CONTRACTS
// ----------------------------
// https://explorer.optimism.io/address/0xf963a0fc0BFc38FEfE08C6062f2AD9A11AfFDEeb
PerpsV2MarketState public constant perpsv2marketstatemkrperp_i =
PerpsV2MarketState(0xf963a0fc0BFc38FEfE08C6062f2AD9A11AfFDEeb);
// https://explorer.optimism.io/address/0x2C15259D4886e2C0946f9aB7a5E389c86b3c3b04
PerpsV2ExchangeRate public constant perpsv2exchangerate_i =
PerpsV2ExchangeRate(0x2C15259D4886e2C0946f9aB7a5E389c86b3c3b04);
// https://explorer.optimism.io/address/0xd30bdFd7e7a65fE109D5dE1D4e95F3B800FB7463
FuturesMarketManager public constant futuresmarketmanager_i =
FuturesMarketManager(0xd30bdFd7e7a65fE109D5dE1D4e95F3B800FB7463);
// https://explorer.optimism.io/address/0x649F44CAC3276557D03223Dbf6395Af65b11c11c
PerpsV2MarketSettings public constant perpsv2marketsettings_i =
PerpsV2MarketSettings(0x649F44CAC3276557D03223Dbf6395Af65b11c11c);
// https://explorer.optimism.io/address/0xE8c41bE1A167314ABAF2423b72Bf8da826943FFD
SystemStatus public constant systemstatus_i = SystemStatus(0xE8c41bE1A167314ABAF2423b72Bf8da826943FFD);
// https://explorer.optimism.io/address/0x2107A107D1043b2c442b8de40d6696C29bD2c5b8
PerpsV2MarketState public constant perpsv2marketstateyfiperp_i =
PerpsV2MarketState(0x2107A107D1043b2c442b8de40d6696C29bD2c5b8);
// https://explorer.optimism.io/address/0xB241aF12256998A0051b93e02027e73CA7E5388d
PerpsV2MarketState public constant perpsv2marketstatemavperp_i =
PerpsV2MarketState(0xB241aF12256998A0051b93e02027e73CA7E5388d);
// https://explorer.optimism.io/address/0xf606E99D6F6a003623eA5764dA119BAEcB2e8C99
PerpsV2MarketState public constant perpsv2marketstaterplperp_i =
PerpsV2MarketState(0xf606E99D6F6a003623eA5764dA119BAEcB2e8C99);
// ----------------------------------
// NEW CONTRACTS DEPLOYED TO BE ADDED
// ----------------------------------
constructor() public BaseMigration(OWNER) {}
function contractsRequiringOwnership() public pure returns (address[] memory contracts) {
contracts = new address[](8);
contracts[0] = address(perpsv2marketstatemkrperp_i);
contracts[1] = address(perpsv2exchangerate_i);
contracts[2] = address(futuresmarketmanager_i);
contracts[3] = address(perpsv2marketsettings_i);
contracts[4] = address(systemstatus_i);
contracts[5] = address(perpsv2marketstateyfiperp_i);
contracts[6] = address(perpsv2marketstatemavperp_i);
contracts[7] = address(perpsv2marketstaterplperp_i);
}
function migrate() external onlyOwner {
// ACCEPT OWNERSHIP for all contracts that require ownership to make changes
acceptAll();
// MIGRATION
// Add migration contract permission to pause
systemstatus_i.updateAccessControl("Futures", address(this), true, false);
perpsv2marketstatemkrperp_i.linkOrInitializeState();
perpsv2exchangerate_addAssociatedContracts_1();
futuresmarketmanager_addProxiedMarkets_2();
perpsv2marketsettings_i.setTakerFee("sMKRPERP", 300000000000000000);
perpsv2marketsettings_i.setMakerFee("sMKRPERP", 300000000000000000);
perpsv2marketsettings_i.setTakerFeeDelayedOrder("sMKRPERP", 300000000000000000);
perpsv2marketsettings_i.setMakerFeeDelayedOrder("sMKRPERP", 300000000000000000);
perpsv2marketsettings_i.setTakerFeeOffchainDelayedOrder("sMKRPERP", 1000000000000000);
perpsv2marketsettings_i.setMakerFeeOffchainDelayedOrder("sMKRPERP", 200000000000000);
perpsv2marketsettings_i.setNextPriceConfirmWindow("sMKRPERP", 2);
perpsv2marketsettings_i.setDelayedOrderConfirmWindow("sMKRPERP", 120);
perpsv2marketsettings_i.setMinDelayTimeDelta("sMKRPERP", 60);
perpsv2marketsettings_i.setMaxDelayTimeDelta("sMKRPERP", 6000);
perpsv2marketsettings_i.setOffchainDelayedOrderMinAge("sMKRPERP", 2);
perpsv2marketsettings_i.setOffchainDelayedOrderMaxAge("sMKRPERP", 60);
perpsv2marketsettings_i.setMaxLeverage("sMKRPERP", 27500000000000000000);
perpsv2marketsettings_i.setMaxMarketValue("sMKRPERP", 750000000000000000000);
perpsv2marketsettings_i.setMaxFundingVelocity("sMKRPERP", 27000000000000000000);
perpsv2marketsettings_i.setSkewScale("sMKRPERP", 60000000000000000000000);
perpsv2marketsettings_i.setOffchainMarketKey("sMKRPERP", "ocMKRPERP");
perpsv2marketsettings_i.setOffchainPriceDivergence("sMKRPERP", 25000000000000000);
perpsv2marketsettings_i.setLiquidationPremiumMultiplier("sMKRPERP", 3000000000000000000);
perpsv2marketsettings_i.setMaxLiquidationDelta("sMKRPERP", 1200000000000000);
perpsv2marketsettings_i.setLiquidationBufferRatio("sMKRPERP", 15000000000000000);
perpsv2marketsettings_i.setMaxPD("sMKRPERP", 2400000000000000);
// Ensure perpsV2 market is paused according to config;
systemstatus_i.suspendFuturesMarket("sMKRPERP", 80);
// Ensure perpsV2 market is paused according to config;
systemstatus_i.suspendFuturesMarket("ocMKRPERP", 80);
perpsv2marketstateyfiperp_i.linkOrInitializeState();
perpsv2exchangerate_addAssociatedContracts_28();
futuresmarketmanager_addProxiedMarkets_29();
perpsv2marketsettings_i.setTakerFee("sYFIPERP", 300000000000000000);
perpsv2marketsettings_i.setMakerFee("sYFIPERP", 300000000000000000);
perpsv2marketsettings_i.setTakerFeeDelayedOrder("sYFIPERP", 300000000000000000);
perpsv2marketsettings_i.setMakerFeeDelayedOrder("sYFIPERP", 300000000000000000);
perpsv2marketsettings_i.setTakerFeeOffchainDelayedOrder("sYFIPERP", 1000000000000000);
perpsv2marketsettings_i.setMakerFeeOffchainDelayedOrder("sYFIPERP", 200000000000000);
perpsv2marketsettings_i.setNextPriceConfirmWindow("sYFIPERP", 2);
perpsv2marketsettings_i.setDelayedOrderConfirmWindow("sYFIPERP", 120);
perpsv2marketsettings_i.setMinDelayTimeDelta("sYFIPERP", 60);
perpsv2marketsettings_i.setMaxDelayTimeDelta("sYFIPERP", 6000);
perpsv2marketsettings_i.setOffchainDelayedOrderMinAge("sYFIPERP", 2);
perpsv2marketsettings_i.setOffchainDelayedOrderMaxAge("sYFIPERP", 60);
perpsv2marketsettings_i.setMaxLeverage("sYFIPERP", 27500000000000000000);
perpsv2marketsettings_i.setMaxMarketValue("sYFIPERP", 75000000000000000000);
perpsv2marketsettings_i.setMaxFundingVelocity("sYFIPERP", 27000000000000000000);
perpsv2marketsettings_i.setSkewScale("sYFIPERP", 2500000000000000000000);
perpsv2marketsettings_i.setOffchainMarketKey("sYFIPERP", "ocYFIPERP");
perpsv2marketsettings_i.setOffchainPriceDivergence("sYFIPERP", 25000000000000000);
perpsv2marketsettings_i.setLiquidationPremiumMultiplier("sYFIPERP", 3000000000000000000);
perpsv2marketsettings_i.setMaxLiquidationDelta("sYFIPERP", 1200000000000000);
perpsv2marketsettings_i.setLiquidationBufferRatio("sYFIPERP", 15000000000000000);
perpsv2marketsettings_i.setMaxPD("sYFIPERP", 2400000000000000);
// Ensure perpsV2 market is paused according to config;
systemstatus_i.suspendFuturesMarket("sYFIPERP", 80);
// Ensure perpsV2 market is paused according to config;
systemstatus_i.suspendFuturesMarket("ocYFIPERP", 80);
perpsv2marketstatemavperp_i.linkOrInitializeState();
perpsv2exchangerate_addAssociatedContracts_55();
futuresmarketmanager_addProxiedMarkets_56();
perpsv2marketsettings_i.setTakerFee("sMAVPERP", 300000000000000000);
perpsv2marketsettings_i.setMakerFee("sMAVPERP", 300000000000000000);
perpsv2marketsettings_i.setTakerFeeDelayedOrder("sMAVPERP", 300000000000000000);
perpsv2marketsettings_i.setMakerFeeDelayedOrder("sMAVPERP", 300000000000000000);
perpsv2marketsettings_i.setTakerFeeOffchainDelayedOrder("sMAVPERP", 1000000000000000);
perpsv2marketsettings_i.setMakerFeeOffchainDelayedOrder("sMAVPERP", 200000000000000);
perpsv2marketsettings_i.setNextPriceConfirmWindow("sMAVPERP", 2);
perpsv2marketsettings_i.setDelayedOrderConfirmWindow("sMAVPERP", 120);
perpsv2marketsettings_i.setMinDelayTimeDelta("sMAVPERP", 60);
perpsv2marketsettings_i.setMaxDelayTimeDelta("sMAVPERP", 6000);
perpsv2marketsettings_i.setOffchainDelayedOrderMinAge("sMAVPERP", 2);
perpsv2marketsettings_i.setOffchainDelayedOrderMaxAge("sMAVPERP", 60);
perpsv2marketsettings_i.setMaxLeverage("sMAVPERP", 27500000000000000000);
perpsv2marketsettings_i.setMaxMarketValue("sMAVPERP", 500000000000000000000000);
perpsv2marketsettings_i.setMaxFundingVelocity("sMAVPERP", 27000000000000000000);
perpsv2marketsettings_i.setSkewScale("sMAVPERP", 21000000000000000000000000);
perpsv2marketsettings_i.setOffchainMarketKey("sMAVPERP", "ocMAVPERP");
perpsv2marketsettings_i.setOffchainPriceDivergence("sMAVPERP", 25000000000000000);
perpsv2marketsettings_i.setLiquidationPremiumMultiplier("sMAVPERP", 3000000000000000000);
perpsv2marketsettings_i.setMaxLiquidationDelta("sMAVPERP", 1200000000000000);
perpsv2marketsettings_i.setLiquidationBufferRatio("sMAVPERP", 15000000000000000);
perpsv2marketsettings_i.setMaxPD("sMAVPERP", 2400000000000000);
// Ensure perpsV2 market is paused according to config;
systemstatus_i.suspendFuturesMarket("sMAVPERP", 80);
// Ensure perpsV2 market is paused according to config;
systemstatus_i.suspendFuturesMarket("ocMAVPERP", 80);
perpsv2marketstaterplperp_i.linkOrInitializeState();
perpsv2exchangerate_addAssociatedContracts_82();
futuresmarketmanager_addProxiedMarkets_83();
perpsv2marketsettings_i.setTakerFee("sRPLPERP", 300000000000000000);
perpsv2marketsettings_i.setMakerFee("sRPLPERP", 300000000000000000);
perpsv2marketsettings_i.setTakerFeeDelayedOrder("sRPLPERP", 300000000000000000);
perpsv2marketsettings_i.setMakerFeeDelayedOrder("sRPLPERP", 300000000000000000);
perpsv2marketsettings_i.setTakerFeeOffchainDelayedOrder("sRPLPERP", 1500000000000000);
perpsv2marketsettings_i.setMakerFeeOffchainDelayedOrder("sRPLPERP", 200000000000000);
perpsv2marketsettings_i.setNextPriceConfirmWindow("sRPLPERP", 2);
perpsv2marketsettings_i.setDelayedOrderConfirmWindow("sRPLPERP", 120);
perpsv2marketsettings_i.setMinDelayTimeDelta("sRPLPERP", 60);
perpsv2marketsettings_i.setMaxDelayTimeDelta("sRPLPERP", 6000);
perpsv2marketsettings_i.setOffchainDelayedOrderMinAge("sRPLPERP", 2);
perpsv2marketsettings_i.setOffchainDelayedOrderMaxAge("sRPLPERP", 60);
perpsv2marketsettings_i.setMaxLeverage("sRPLPERP", 27500000000000000000);
perpsv2marketsettings_i.setMaxMarketValue("sRPLPERP", 3000000000000000000000);
perpsv2marketsettings_i.setMaxFundingVelocity("sRPLPERP", 27000000000000000000);
perpsv2marketsettings_i.setSkewScale("sRPLPERP", 17500000000000000000000);
perpsv2marketsettings_i.setOffchainMarketKey("sRPLPERP", "ocRPLPERP");
perpsv2marketsettings_i.setOffchainPriceDivergence("sRPLPERP", 25000000000000000);
perpsv2marketsettings_i.setLiquidationPremiumMultiplier("sRPLPERP", 3000000000000000000);
perpsv2marketsettings_i.setMaxLiquidationDelta("sRPLPERP", 1700000000000000);
perpsv2marketsettings_i.setLiquidationBufferRatio("sRPLPERP", 15000000000000000);
perpsv2marketsettings_i.setMaxPD("sRPLPERP", 3400000000000000);
// Ensure perpsV2 market is paused according to config;
systemstatus_i.suspendFuturesMarket("sRPLPERP", 80);
// Ensure perpsV2 market is paused according to config;
systemstatus_i.suspendFuturesMarket("ocRPLPERP", 80);
// Remove permission to migration contract
systemstatus_i.updateAccessControl("Futures", address(this), false, false);
// NOMINATE OWNERSHIP back to owner for aforementioned contracts
nominateAll();
}
function acceptAll() internal {
address[] memory contracts = contractsRequiringOwnership();
for (uint i = 0; i < contracts.length; i++) {
Owned(contracts[i]).acceptOwnership();
}
}
function nominateAll() internal {
address[] memory contracts = contractsRequiringOwnership();
for (uint i = 0; i < contracts.length; i++) {
returnOwnership(contracts[i]);
}
}
function perpsv2exchangerate_addAssociatedContracts_1() internal {
address[] memory perpsv2exchangerate_addAssociatedContracts_associatedContracts_1_0 = new address[](2);
perpsv2exchangerate_addAssociatedContracts_associatedContracts_1_0[0] = address(
0xDEbC936c5aDfd1331E5fa4AE76DB7197283342d0
);
perpsv2exchangerate_addAssociatedContracts_associatedContracts_1_0[1] = address(
0xE0d1A14EBC3bc4460fEeB67A45C8198063cCC7c7
);
perpsv2exchangerate_i.addAssociatedContracts(perpsv2exchangerate_addAssociatedContracts_associatedContracts_1_0);
}
function futuresmarketmanager_addProxiedMarkets_2() internal {
address[] memory futuresmarketmanager_addProxiedMarkets_marketsToAdd_2_0 = new address[](1);
futuresmarketmanager_addProxiedMarkets_marketsToAdd_2_0[0] = address(0xf7d9Bd13F877171f6C7f93F71bdf8e380335dc12);
futuresmarketmanager_i.addProxiedMarkets(futuresmarketmanager_addProxiedMarkets_marketsToAdd_2_0);
}
function perpsv2exchangerate_addAssociatedContracts_28() internal {
address[] memory perpsv2exchangerate_addAssociatedContracts_associatedContracts_28_0 = new address[](2);
perpsv2exchangerate_addAssociatedContracts_associatedContracts_28_0[0] = address(
0xf7AF14838789093ccD01c67cF9Bc5f602501cEd0
);
perpsv2exchangerate_addAssociatedContracts_associatedContracts_28_0[1] = address(
0x7aF6Be46f83d25902cfa49c9e16BEc54893f25cB
);
perpsv2exchangerate_i.addAssociatedContracts(perpsv2exchangerate_addAssociatedContracts_associatedContracts_28_0);
}
function futuresmarketmanager_addProxiedMarkets_29() internal {
address[] memory futuresmarketmanager_addProxiedMarkets_marketsToAdd_29_0 = new address[](1);
futuresmarketmanager_addProxiedMarkets_marketsToAdd_29_0[0] = address(0x6940e7C6125a177b052C662189bb27692E88E9Cb);
futuresmarketmanager_i.addProxiedMarkets(futuresmarketmanager_addProxiedMarkets_marketsToAdd_29_0);
}
function perpsv2exchangerate_addAssociatedContracts_55() internal {
address[] memory perpsv2exchangerate_addAssociatedContracts_associatedContracts_55_0 = new address[](2);
perpsv2exchangerate_addAssociatedContracts_associatedContracts_55_0[0] = address(
0x6A5A1E32216377FC03bFFdC9B33fe29c2f14Ec84
);
perpsv2exchangerate_addAssociatedContracts_associatedContracts_55_0[1] = address(
0xFc6895ff4756985BCa9df2AABB5f31651C591Bef
);
perpsv2exchangerate_i.addAssociatedContracts(perpsv2exchangerate_addAssociatedContracts_associatedContracts_55_0);
}
function futuresmarketmanager_addProxiedMarkets_56() internal {
address[] memory futuresmarketmanager_addProxiedMarkets_marketsToAdd_56_0 = new address[](1);
futuresmarketmanager_addProxiedMarkets_marketsToAdd_56_0[0] = address(0x572F816F21F56D47e4c4fA577837bd3f58088676);
futuresmarketmanager_i.addProxiedMarkets(futuresmarketmanager_addProxiedMarkets_marketsToAdd_56_0);
}
function perpsv2exchangerate_addAssociatedContracts_82() internal {
address[] memory perpsv2exchangerate_addAssociatedContracts_associatedContracts_82_0 = new address[](2);
perpsv2exchangerate_addAssociatedContracts_associatedContracts_82_0[0] = address(
0xc9c64cF6D1CE4b41D087F08EdAa9De23262f1EdA
);
perpsv2exchangerate_addAssociatedContracts_associatedContracts_82_0[1] = address(
0xF0671cF8a1a0b3308e84852308F9624B9eC2e28f
);
perpsv2exchangerate_i.addAssociatedContracts(perpsv2exchangerate_addAssociatedContracts_associatedContracts_82_0);
}
function futuresmarketmanager_addProxiedMarkets_83() internal {
address[] memory futuresmarketmanager_addProxiedMarkets_marketsToAdd_83_0 = new address[](1);
futuresmarketmanager_addProxiedMarkets_marketsToAdd_83_0[0] = address(0xfAD0835dAD2985b25ddab17eace356237589E5C7);
futuresmarketmanager_i.addProxiedMarkets(futuresmarketmanager_addProxiedMarkets_marketsToAdd_83_0);
}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
./pragma solidity ^0.5.16;
import "./BaseMigration.sol";
import "./AddressResolver.sol";
interface ISynthetixNamedContract {
// solhint-disable func-name-mixedcase
function CONTRACT_NAME() external view returns (bytes32);
}
// solhint-disable contract-name-camelcase
contract Migration_Caph is BaseMigration {
// https://etherscan.io/address/0xEb3107117FEAd7de89Cd14D463D340A2E6917769;
address public constant OWNER = 0xEb3107117FEAd7de89Cd14D463D340A2E6917769;
// ----------------------------
// EXISTING SYNTHETIX CONTRACTS
// ----------------------------
// https://etherscan.io/address/0x823bE81bbF96BEc0e25CA13170F5AaCb5B79ba83
AddressResolver public constant addressresolver_i = AddressResolver(0x823bE81bbF96BEc0e25CA13170F5AaCb5B79ba83);
// ----------------------------------
// NEW CONTRACTS DEPLOYED TO BE ADDED
// ----------------------------------
// https://etherscan.io/address/0xd3f527F47A9DF2f6cBf631081315b6e2FE4e4521
address public constant new_FuturesMarketManager_contract = 0xd3f527F47A9DF2f6cBf631081315b6e2FE4e4521;
constructor() public BaseMigration(OWNER) {}
function contractsRequiringOwnership() public pure returns (address[] memory contracts) {
contracts = new address[](1);
contracts[0] = address(addressresolver_i);
}
function migrate() external onlyOwner {
// ACCEPT OWNERSHIP for all contracts that require ownership to make changes
acceptAll();
// MIGRATION
// Import all new contracts into the address resolver;
addressresolver_importAddresses_0();
// Rebuild the resolver caches in all MixinResolver contracts - batch 1;
addressresolver_rebuildCaches_1();
// NOMINATE OWNERSHIP back to owner for aforementioned contracts
nominateAll();
}
function acceptAll() internal {
address[] memory contracts = contractsRequiringOwnership();
for (uint i = 0; i < contracts.length; i++) {
Owned(contracts[i]).acceptOwnership();
}
}
function nominateAll() internal {
address[] memory contracts = contractsRequiringOwnership();
for (uint i = 0; i < contracts.length; i++) {
returnOwnership(contracts[i]);
}
}
function addressresolver_importAddresses_0() internal {
bytes32[] memory addressresolver_importAddresses_names_0_0 = new bytes32[](1);
addressresolver_importAddresses_names_0_0[0] = bytes32("FuturesMarketManager");
address[] memory addressresolver_importAddresses_destinations_0_1 = new address[](1);
addressresolver_importAddresses_destinations_0_1[0] = address(new_FuturesMarketManager_contract);
addressresolver_i.importAddresses(
addressresolver_importAddresses_names_0_0,
addressresolver_importAddresses_destinations_0_1
);
}
function addressresolver_rebuildCaches_1() internal {
MixinResolver[] memory addressresolver_rebuildCaches_destinations_1_0 = new MixinResolver[](12);
addressresolver_rebuildCaches_destinations_1_0[0] = MixinResolver(0x83105D7CDd2fd9b8185BFF1cb56bB1595a618618);
addressresolver_rebuildCaches_destinations_1_0[1] = MixinResolver(0x1620Aa736939597891C1940CF0d28b82566F9390);
addressresolver_rebuildCaches_destinations_1_0[2] = MixinResolver(0x10A5F7D9D65bCc2734763444D4940a31b109275f);
addressresolver_rebuildCaches_destinations_1_0[3] = MixinResolver(0xa8E31E3C38aDD6052A9407298FAEB8fD393A6cF9);
addressresolver_rebuildCaches_destinations_1_0[4] = MixinResolver(0xE1cc2332852B2Ac0dA59A1f9D3051829f4eF3c1C);
addressresolver_rebuildCaches_destinations_1_0[5] = MixinResolver(0xfb020CA7f4e8C4a5bBBe060f59a249c6275d2b69);
addressresolver_rebuildCaches_destinations_1_0[6] = MixinResolver(0xdc883b9d9Ee16f74bE08826E68dF4C9D9d26e8bD);
addressresolver_rebuildCaches_destinations_1_0[7] = MixinResolver(0xBb5b03E920cF702De5A3bA9Fc1445aF4B3919c88);
addressresolver_rebuildCaches_destinations_1_0[8] = MixinResolver(0xdAe6C79c46aB3B280Ca28259000695529cbD1339);
addressresolver_rebuildCaches_destinations_1_0[9] = MixinResolver(0x1cB004a8e84a5CE95C1fF895EE603BaC8EC506c7);
addressresolver_rebuildCaches_destinations_1_0[10] = MixinResolver(0x5D4C724BFe3a228Ff0E29125Ac1571FE093700a4);
addressresolver_rebuildCaches_destinations_1_0[11] = MixinResolver(0x07C1E81C345A7c58d7c24072EFc5D929BD0647AD);
addressresolver_i.rebuildCaches(addressresolver_rebuildCaches_destinations_1_0);
}
}
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
import "./BaseMigration.sol";
import "./FuturesMarketManager.sol";
import "./AddressResolver.sol";
interface ISynthetixNamedContract {
// solhint-disable func-name-mixedcase
function CONTRACT_NAME() external view returns (bytes32);
}
// solhint-disable contract-name-camelcase
contract Migration_CaphOptimismStep1 is BaseMigration {
// https://explorer.optimism.io/address/0x6d4a64C57612841c2C6745dB2a4E4db34F002D20;
address public constant OWNER = 0x6d4a64C57612841c2C6745dB2a4E4db34F002D20;
// ----------------------------
// EXISTING SYNTHETIX CONTRACTS
// ----------------------------
// https://explorer.optimism.io/address/0xd30bdFd7e7a65fE109D5dE1D4e95F3B800FB7463
FuturesMarketManager public constant futuresmarketmanager_i =
FuturesMarketManager(0xd30bdFd7e7a65fE109D5dE1D4e95F3B800FB7463);
// https://explorer.optimism.io/address/0x95A6a3f44a70172E7d50a9e28c85Dfd712756B8C
AddressResolver public constant addressresolver_i = AddressResolver(0x95A6a3f44a70172E7d50a9e28c85Dfd712756B8C);
// ----------------------------------
// NEW CONTRACTS DEPLOYED TO BE ADDED
// ----------------------------------
// https://explorer.optimism.io/address/0xd30bdFd7e7a65fE109D5dE1D4e95F3B800FB7463
address public constant new_FuturesMarketManager_contract = 0xd30bdFd7e7a65fE109D5dE1D4e95F3B800FB7463;
// https://explorer.optimism.io/address/0x58e6227510F83d3F45B339F2f7A05a699fDEE6D4
address public constant new_PerpsV2MarketData_contract = 0x58e6227510F83d3F45B339F2f7A05a699fDEE6D4;
// https://explorer.optimism.io/address/0x2C15259D4886e2C0946f9aB7a5E389c86b3c3b04
address public constant new_PerpsV2ExchangeRate_contract = 0x2C15259D4886e2C0946f9aB7a5E389c86b3c3b04;
// https://explorer.optimism.io/address/0x649F44CAC3276557D03223Dbf6395Af65b11c11c
address public constant new_PerpsV2MarketSettings_contract = 0x649F44CAC3276557D03223Dbf6395Af65b11c11c;
constructor() public BaseMigration(OWNER) {}
function contractsRequiringOwnership() public pure returns (address[] memory contracts) {
contracts = new address[](2);
contracts[0] = address(futuresmarketmanager_i);
contracts[1] = address(addressresolver_i);
}
function migrate() external onlyOwner {
// ACCEPT OWNERSHIP for all contracts that require ownership to make changes
acceptAll();
// MIGRATION
futuresmarketmanager_addMarkets_0();
futuresmarketmanager_addProxiedMarkets_1();
futuresmarketmanager_addProxiedMarkets_2();
futuresmarketmanager_addProxiedMarkets_3();
// Import all new contracts into the address resolver;
addressresolver_importAddresses_4();
// Import all new contracts into the address resolver;
addressresolver_importAddresses_5();
// Rebuild the resolver caches in all MixinResolver contracts - batch 1;
addressresolver_rebuildCaches_6();
// NOMINATE OWNERSHIP back to owner for aforementioned contracts
nominateAll();
}
function acceptAll() internal {
address[] memory contracts = contractsRequiringOwnership();
for (uint i = 0; i < contracts.length; i++) {
Owned(contracts[i]).acceptOwnership();
}
}
function nominateAll() internal {
address[] memory contracts = contractsRequiringOwnership();
for (uint i = 0; i < contracts.length; i++) {
returnOwnership(contracts[i]);
}
}
function futuresmarketmanager_addMarkets_0() internal {
address[] memory futuresmarketmanager_addMarkets_marketsToAdd_0_0 = new address[](18);
futuresmarketmanager_addMarkets_marketsToAdd_0_0[0] = address(0xEe8804d8Ad10b0C3aD1Bd57AC3737242aD24bB95);
futuresmarketmanager_addMarkets_marketsToAdd_0_0[1] = address(0xf86048DFf23cF130107dfB4e6386f574231a5C65);
futuresmarketmanager_addMarkets_marketsToAdd_0_0[2] = address(0x1228c7D8BBc5bC53DB181bD7B1fcE765aa83bF8A);
futuresmarketmanager_addMarkets_marketsToAdd_0_0[3] = address(0xcF853f7f8F78B2B801095b66F8ba9c5f04dB1640);
futuresmarketmanager_addMarkets_marketsToAdd_0_0[4] = address(0x4ff54624D5FB61C34c634c3314Ed3BfE4dBB665a);
futuresmarketmanager_addMarkets_marketsToAdd_0_0[5] = address(0x001b7876F567f0b3A639332Ed1e363839c6d85e2);
futuresmarketmanager_addMarkets_marketsToAdd_0_0[6] = address(0x5Af0072617F7f2AEB0e314e2faD1DE0231Ba97cD);
futuresmarketmanager_addMarkets_marketsToAdd_0_0[7] = address(0xbCB2D435045E16B059b2130b28BE70b5cA47bFE5);
futuresmarketmanager_addMarkets_marketsToAdd_0_0[8] = address(0x4434f56ddBdE28fab08C4AE71970a06B300F8881);
futuresmarketmanager_addMarkets_marketsToAdd_0_0[9] = address(0xb147C69BEe211F57290a6cde9d1BAbfD0DCF3Ea3);
futuresmarketmanager_addMarkets_marketsToAdd_0_0[10] = address(0xad44873632840144fFC97b2D1de716f6E2cF0366);
futuresmarketmanager_addMarkets_marketsToAdd_0_0[11] = address(0xFe00395ec846240dc693e92AB2Dd720F94765Aa3);
futuresmarketmanager_addMarkets_marketsToAdd_0_0[12] = address(0x10305C1854d6DB8A1060dF60bDF8A8B2981249Cf);
futuresmarketmanager_addMarkets_marketsToAdd_0_0[13] = address(0x4Aa0dabd22BC0894975324Bec293443c8538bD08);
futuresmarketmanager_addMarkets_marketsToAdd_0_0[14] = address(0x9F1C2f0071Bc3b31447AEda9fA3A68d651eB4632);
futuresmarketmanager_addMarkets_marketsToAdd_0_0[15] = address(0x3Ed04CEfF4c91872F19b1da35740C0Be9CA21558);
futuresmarketmanager_addMarkets_marketsToAdd_0_0[16] = address(0x9f231dBE53D460f359B2B8CC47574493caA5B7Bf);
futuresmarketmanager_addMarkets_marketsToAdd_0_0[17] = address(0xd325B17d5C9C3f2B6853A760afCF81945b0184d3);
futuresmarketmanager_i.addMarkets(futuresmarketmanager_addMarkets_marketsToAdd_0_0);
}
function futuresmarketmanager_addProxiedMarkets_1() internal {
address[] memory futuresmarketmanager_addProxiedMarkets_marketsToAdd_1_0 = new address[](10);
futuresmarketmanager_addProxiedMarkets_marketsToAdd_1_0[0] = address(0x2B3bb4c683BFc5239B029131EEf3B1d214478d93);
futuresmarketmanager_addProxiedMarkets_marketsToAdd_1_0[1] = address(0x59b007E9ea8F89b069c43F8f45834d30853e3699);
futuresmarketmanager_addProxiedMarkets_marketsToAdd_1_0[2] = address(0x31A1659Ca00F617E86Dc765B6494Afe70a5A9c1A);
futuresmarketmanager_addProxiedMarkets_marketsToAdd_1_0[3] = address(0x0EA09D97b4084d859328ec4bF8eBCF9ecCA26F1D);
futuresmarketmanager_addProxiedMarkets_marketsToAdd_1_0[4] = address(0xc203A12F298CE73E44F7d45A4f59a43DBfFe204D);
futuresmarketmanager_addProxiedMarkets_marketsToAdd_1_0[5] = address(0x5374761526175B59f1E583246E20639909E189cE);
futuresmarketmanager_addProxiedMarkets_marketsToAdd_1_0[6] = address(0x4308427C463CAEAaB50FFf98a9deC569C31E4E87);
futuresmarketmanager_addProxiedMarkets_marketsToAdd_1_0[7] = address(0x074B8F19fc91d6B2eb51143E1f186Ca0DDB88042);
futuresmarketmanager_addProxiedMarkets_marketsToAdd_1_0[8] = address(0x5B6BeB79E959Aac2659bEE60fE0D0885468BF886);
futuresmarketmanager_addProxiedMarkets_marketsToAdd_1_0[9] = address(0x139F94E4f0e1101c1464a321CBA815c34d58B5D9);
futuresmarketmanager_i.addProxiedMarkets(futuresmarketmanager_addProxiedMarkets_marketsToAdd_1_0);
}
function futuresmarketmanager_addProxiedMarkets_2() internal {
address[] memory futuresmarketmanager_addProxiedMarkets_marketsToAdd_2_0 = new address[](10);
futuresmarketmanager_addProxiedMarkets_marketsToAdd_2_0[0] = address(0x0940B0A96C5e1ba33AEE331a9f950Bb2a6F2Fb25);
futuresmarketmanager_addProxiedMarkets_marketsToAdd_2_0[1] = address(0x442b69937a0daf9D46439a71567fABE6Cb69FBaf);
futuresmarketmanager_addProxiedMarkets_marketsToAdd_2_0[2] = address(0x98cCbC721cc05E28a125943D69039B39BE6A21e9);
futuresmarketmanager_addProxiedMarkets_marketsToAdd_2_0[3] = address(0x549dbDFfbd47bD5639f9348eBE82E63e2f9F777A);
futuresmarketmanager_addProxiedMarkets_marketsToAdd_2_0[4] = address(0xdcB8438c979fA030581314e5A5Df42bbFEd744a0);
futuresmarketmanager_addProxiedMarkets_marketsToAdd_2_0[5] = address(0x87AE62c5720DAB812BDacba66cc24839440048d1);
futuresmarketmanager_addProxiedMarkets_marketsToAdd_2_0[6] = address(0xbB16C7B3244DFA1a6BF83Fcce3EE4560837763CD);
futuresmarketmanager_addProxiedMarkets_marketsToAdd_2_0[7] = address(0x3a52b21816168dfe35bE99b7C5fc209f17a0aDb1);
futuresmarketmanager_addProxiedMarkets_marketsToAdd_2_0[8] = address(0x27665271210aCff4Fab08AD9Bb657E91866471F0);
futuresmarketmanager_addProxiedMarkets_marketsToAdd_2_0[9] = address(0xC18f85A6DD3Bcd0516a1CA08d3B1f0A4E191A2C4);
futuresmarketmanager_i.addProxiedMarkets(futuresmarketmanager_addProxiedMarkets_marketsToAdd_2_0);
}
function futuresmarketmanager_addProxiedMarkets_3() internal {
address[] memory futuresmarketmanager_addProxiedMarkets_marketsToAdd_3_0 = new address[](4);
futuresmarketmanager_addProxiedMarkets_marketsToAdd_3_0[0] = address(0xC8fCd6fB4D15dD7C455373297dEF375a08942eCe);
futuresmarketmanager_addProxiedMarkets_marketsToAdd_3_0[1] = address(0x9De146b5663b82F44E5052dEDe2aA3Fd4CBcDC99);
futuresmarketmanager_addProxiedMarkets_marketsToAdd_3_0[2] = address(0x1dAd8808D8aC58a0df912aDC4b215ca3B93D6C49);
futuresmarketmanager_addProxiedMarkets_marketsToAdd_3_0[3] = address(0x509072A5aE4a87AC89Fc8D64D94aDCb44Bd4b88e);
futuresmarketmanager_i.addProxiedMarkets(futuresmarketmanager_addProxiedMarkets_marketsToAdd_3_0);
}
function addressresolver_importAddresses_4() internal {
bytes32[] memory addressresolver_importAddresses_names_4_0 = new bytes32[](4);
addressresolver_importAddresses_names_4_0[0] = bytes32("PerpsV2MarketData");
addressresolver_importAddresses_names_4_0[1] = bytes32("FuturesMarketManager");
addressresolver_importAddresses_names_4_0[2] = bytes32("PerpsV2MarketSettings");
addressresolver_importAddresses_names_4_0[3] = bytes32("PerpsV2ExchangeRate");
address[] memory addressresolver_importAddresses_destinations_4_1 = new address[](4);
addressresolver_importAddresses_destinations_4_1[0] = address(new_PerpsV2MarketData_contract);
addressresolver_importAddresses_destinations_4_1[1] = address(new_FuturesMarketManager_contract);
addressresolver_importAddresses_destinations_4_1[2] = address(new_PerpsV2MarketSettings_contract);
addressresolver_importAddresses_destinations_4_1[3] = address(new_PerpsV2ExchangeRate_contract);
addressresolver_i.importAddresses(
addressresolver_importAddresses_names_4_0,
addressresolver_importAddresses_destinations_4_1
);
}
function addressresolver_importAddresses_5() internal {
bytes32[] memory addressresolver_importAddresses_names_5_0 = new bytes32[](4);
addressresolver_importAddresses_names_5_0[0] = bytes32("FuturesMarketManager");
addressresolver_importAddresses_names_5_0[1] = bytes32("PerpsV2MarketData");
addressresolver_importAddresses_names_5_0[2] = bytes32("PerpsV2ExchangeRate");
addressresolver_importAddresses_names_5_0[3] = bytes32("PerpsV2MarketSettings");
address[] memory addressresolver_importAddresses_destinations_5_1 = new address[](4);
addressresolver_importAddresses_destinations_5_1[0] = address(new_FuturesMarketManager_contract);
addressresolver_importAddresses_destinations_5_1[1] = address(new_PerpsV2MarketData_contract);
addressresolver_importAddresses_destinations_5_1[2] = address(new_PerpsV2ExchangeRate_contract);
addressresolver_importAddresses_destinations_5_1[3] = address(new_PerpsV2MarketSettings_contract);
addressresolver_i.importAddresses(
addressresolver_importAddresses_names_5_0,
addressresolver_importAddresses_destinations_5_1
);
}
function addressresolver_rebuildCaches_6() internal {
MixinResolver[] memory addressresolver_rebuildCaches_destinations_6_0 = new MixinResolver[](10);
addressresolver_rebuildCaches_destinations_6_0[0] = MixinResolver(0xf9FE3607e6d19D8dC690DD976061a91D4A0db30B);
addressresolver_rebuildCaches_destinations_6_0[1] = MixinResolver(0x17628A557d1Fc88D1c35989dcBAC3f3e275E2d2B);
addressresolver_rebuildCaches_destinations_6_0[2] = MixinResolver(0xDfA2d3a0d32F870D87f8A0d7AA6b9CdEB7bc5AdB);
addressresolver_rebuildCaches_destinations_6_0[3] = MixinResolver(0xe9dceA0136FEFC76c4E639Ec60CCE70482E2aCF7);
addressresolver_rebuildCaches_destinations_6_0[4] = MixinResolver(0x421DEF861D623F7123dfE0878D86E9576cbb3975);
addressresolver_rebuildCaches_destinations_6_0[5] = MixinResolver(0xdEdb0b04AFF1525bb4B6167F00e61601690c1fF2);
addressresolver_rebuildCaches_destinations_6_0[6] = MixinResolver(0x34c2360ffe5D21542f76e991FFD104f281D4B3fb);
addressresolver_rebuildCaches_destinations_6_0[7] = MixinResolver(new_PerpsV2MarketSettings_contract);
addressresolver_rebuildCaches_destinations_6_0[8] = MixinResolver(new_FuturesMarketManager_contract);
addressresolver_rebuildCaches_destinations_6_0[9] = MixinResolver(new_PerpsV2ExchangeRate_contract);
addressresolver_i.rebuildCaches(addressresolver_rebuildCaches_destinations_6_0);
}
}
View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

This file has been truncated, but you can view the full file.
/*
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██████ ███████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████ ██ ██ █████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██████ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██████ ███████ ████
Find any smart contract, and build your project faster: https://www.cookbook.dev
Twitter: https://twitter.com/cookbook_dev
Discord: https://discord.gg/WzsfPcfHrk
Find this contract on Cookbook: https://www.cookbook.dev/protocols/Synthetix/?utm=code
*/
pragma solidity ^0.5.16;
import "./BaseMigration.sol";
import "./SystemStatus.sol";
import "./PerpsV2MarketState.sol";
import "./PerpsV2ExchangeRate.sol";
import "./ProxyPerpsV2.sol";
import "./FuturesMarketManager.sol";
import "./PerpsV2MarketSettings.sol";
interface ISynthetixNamedContract {
// solhint-disable func-name-mixedcase
function CONTRACT_NAME() external view returns (bytes32);
}
// solhint-disable contract-name-camelcase
contract Migration_CaphOptimismStep10 is BaseMigration {
// https://explorer.optimism.io/address/0x6d4a64C57612841c2C6745dB2a4E4db34F002D20;
address public constant OWNER = 0x6d4a64C57612841c2C6745dB2a4E4db34F002D20;
// ----------------------------
// EXISTING SYNTHETIX CONTRACTS
// ----------------------------
// https://explorer.optimism.io/address/0xE8c41bE1A167314ABAF2423b72Bf8da826943FFD
SystemStatus public constant systemstatus_i = SystemStatus(0xE8c41bE1A167314ABAF2423b72Bf8da826943FFD);
// https://explorer.optimism.io/address/0xEed3618dd59163CC6849758F07fA9369823aa710
PerpsV2MarketState public constant perpsv2marketstatelinkperp_i =
PerpsV2MarketState(0xEed3618dd59163CC6849758F07fA9369823aa710);
// https://explorer.optimism.io/address/0x49dC714eaD0cc585eBaC8A412098914a2CE7B7B2
PerpsV2MarketState public constant perpsv2marketstatelinkperplegacy_i =
PerpsV2MarketState(0x49dC714eaD0cc585eBaC8A412098914a2CE7B7B2);
// https://explorer.optimism.io/address/0x2C15259D4886e2C0946f9aB7a5E389c86b3c3b04
PerpsV2ExchangeRate public constant perpsv2exchangerate_i =
PerpsV2ExchangeRate(0x2C15259D4886e2C0946f9aB7a5E389c86b3c3b04);
// https://explorer.optimism.io/address/0x31A1659Ca00F617E86Dc765B6494Afe70a5A9c1A
ProxyPerpsV2 public constant perpsv2proxylinkperp_i = ProxyPerpsV2(0x31A1659Ca00F617E86Dc765B6494Afe70a5A9c1A);
// https://explorer.optimism.io/address/0xd30bdFd7e7a65fE109D5dE1D4e95F3B800FB7463
FuturesMarketManager public constant futuresmarketmanager_i =
FuturesMarketManager(0xd30bdFd7e7a65fE109D5dE1D4e95F3B800FB7463);
// https://explorer.optimism.io/address/0x649F44CAC3276557D03223Dbf6395Af65b11c11c
PerpsV2MarketSettings public constant perpsv2marketsettings_i =
PerpsV2MarketSettings(0x649F44CAC3276557D03223Dbf6395Af65b11c11c);
// https://explorer.optimism.io/address/0xBdD0D09f73AC6f8Ef59A71baab283C12dcab06fA
PerpsV2MarketState public constant perpsv2marketstateopperp_i =
PerpsV2MarketState(0xBdD0D09f73AC6f8Ef59A71baab283C12dcab06fA);
// https://explorer.optimism.io/address/0xa26c97A0c9788e937986ee6276f3762c20C06ef5
PerpsV2MarketState public constant perpsv2marketstateopperplegacy_i =
PerpsV2MarketState(0xa26c97A0c9788e937986ee6276f3762c20C06ef5);
// https://explorer.optimism.io/address/0x442b69937a0daf9D46439a71567fABE6Cb69FBaf
ProxyPerpsV2 public constant perpsv2proxyopperp_i = ProxyPerpsV2(0x442b69937a0daf9D46439a71567fABE6Cb69FBaf);
// ----------------------------------
// NEW CONTRACTS DEPLOYED TO BE ADDED
// ----------------------------------
constructor() public BaseMigration(OWNER) {}
function contractsRequiringOwnership() public pure returns (address[] memory contracts) {
contracts = new address[](10);
contracts[0] = address(systemstatus_i);
contracts[1] = address(perpsv2marketstatelinkperp_i);
contracts[2] = address(perpsv2marketstatelinkperplegacy_i);
contracts[3] = address(perpsv2exchangerate_i);
contracts[4] = address(perpsv2proxylinkperp_i);
contracts[5] = address(futuresmarketmanager_i);
contracts[6] = address(perpsv2marketsettings_i);
contracts[7] = address(perpsv2marketstateopperp_i);
contracts[8] = address(perpsv2marketstateopperplegacy_i);
contracts[9] = address(perpsv2proxyopperp_i);
}
function migrate() external onlyOwner {
// ACCEPT OWNERSHIP for all contracts that require ownership to make changes
acceptAll();
// MIGRATION
systemstatus_i.updateAccessControl("Futures", address(this), true, true);
perpsv2marketstatelinkperp_i.linkOrInitializeState();
perpsv2marketstatelinkperplegacy_addAssociatedContracts_2();
perpsv2exchangerate_addAssociatedContracts_3();
perpsv2proxylinkperp_i.addRoute(0xa126d601, 0xD86e7B02cbaCb63eF6E91915205F7b122b8a8C3a, false);
perpsv2proxylinkperp_i.addRoute(0x5c8011c3, 0xD86e7B02cbaCb63eF6E91915205F7b122b8a8C3a, false);
perpsv2proxylinkperp_i.addRoute(0x2af64bd3, 0xD86e7B02cbaCb63eF6E91915205F7b122b8a8C3a, true);
perpsv2proxylinkperp_i.addRoute(0xd67bdd25, 0xD86e7B02cbaCb63eF6E91915205F7b122b8a8C3a, true);
perpsv2proxylinkperp_i.addRoute(0x4ad4914b, 0xD86e7B02cbaCb63eF6E91915205F7b122b8a8C3a, false);
perpsv2proxylinkperp_i.addRoute(0x32f05103, 0xD86e7B02cbaCb63eF6E91915205F7b122b8a8C3a, false);
perpsv2proxylinkperp_i.addRoute(0xec556889, 0xD86e7B02cbaCb63eF6E91915205F7b122b8a8C3a, true);
perpsv2proxylinkperp_i.addRoute(0x4eb985cc, 0xD86e7B02cbaCb63eF6E91915205F7b122b8a8C3a, false);
perpsv2proxylinkperp_i.addRoute(0xbc67f832, 0xD86e7B02cbaCb63eF6E91915205F7b122b8a8C3a, false);
perpsv2proxylinkperp_i.addRoute(0x97107d6d, 0xD86e7B02cbaCb63eF6E91915205F7b122b8a8C3a, false);
perpsv2proxylinkperp_i.addRoute(0x88a3c848, 0xD86e7B02cbaCb63eF6E91915205F7b122b8a8C3a, false);
perpsv2proxylinkperp_i.addRoute(0x5a1cbd2b, 0xD86e7B02cbaCb63eF6E91915205F7b122b8a8C3a, false);
perpsv2proxylinkperp_i.addRoute(0x909bc379, 0x360Bc3aCB4fEA8112D8Ac20CE1E93b9B70C3d85a, false);
perpsv2proxylinkperp_i.addRoute(0x3c92b8ec, 0x360Bc3aCB4fEA8112D8Ac20CE1E93b9B70C3d85a, false);
View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment