Contract 0xb9713D033DF6190D941F169cDEDc1C69B5314e72

Contract Overview

Balance:
0 Ether
Txn Hash
Method
Block
From
To
Value
0x2d69d98a7c26d3aba0ec10ca66fce47156ee35f41a760625e8fb8e8546f3206bExchange Atomica...323223642022-06-22 20:23:5612 days 3 hrs ago0x9e988bf30fa85c5ee2ceac5c13c95357cd35d0f3 IN  0xb9713d033df6190d941f169cdedc1c69b5314e720 Ether0.00003803 1
0x5a7eae24446f46cba2586546ed00e9a3051f391f869dc6af11b7245a300af6cb0x60806040315767802022-05-13 5:40:1652 days 18 hrs ago0x73570075092502472e4b61a7058df1a4a1db12f2 IN  Create: ExchangerWithFeeRecAlternatives0 Ether0.00506893 1
[ Download CSV Export 
Latest 25 internal transaction
Parent Txn Hash Block From To Value
0x8656e32929a79463b0991ed712e8f83822381cc061bf1121d5d7224fff429b3a325438102022-07-04 15:37:128 hrs 46 mins ago 0xb9713d033df6190d941f169cdedc1c69b5314e72 0xa3f59b8e28cabc4411198dda2e65c380bd5d6dfe0 Ether
0x8656e32929a79463b0991ed712e8f83822381cc061bf1121d5d7224fff429b3a325438102022-07-04 15:37:128 hrs 46 mins ago 0x4bf55262c17388c13cdd9538a830b32191493667 0xb9713d033df6190d941f169cdedc1c69b5314e720 Ether
0x8656e32929a79463b0991ed712e8f83822381cc061bf1121d5d7224fff429b3a325438102022-07-04 15:37:128 hrs 46 mins ago 0xb9713d033df6190d941f169cdedc1c69b5314e72 0xb1751e5ede811288ce2fc4c65aaca17a955366be0 Ether
0x8656e32929a79463b0991ed712e8f83822381cc061bf1121d5d7224fff429b3a325438102022-07-04 15:37:128 hrs 46 mins ago 0xb9713d033df6190d941f169cdedc1c69b5314e72 0xa3f59b8e28cabc4411198dda2e65c380bd5d6dfe0 Ether
0x8656e32929a79463b0991ed712e8f83822381cc061bf1121d5d7224fff429b3a325438102022-07-04 15:37:128 hrs 46 mins ago 0x4bf55262c17388c13cdd9538a830b32191493667 0xb9713d033df6190d941f169cdedc1c69b5314e720 Ether
0x661f74c00363f04c8acb143384964c344afd5913263fbfa7cbf880773d70e53d325356132022-07-04 4:58:5219 hrs 24 mins ago 0xb9713d033df6190d941f169cdedc1c69b5314e72 0xa3f59b8e28cabc4411198dda2e65c380bd5d6dfe0 Ether
0x661f74c00363f04c8acb143384964c344afd5913263fbfa7cbf880773d70e53d325356132022-07-04 4:58:5219 hrs 24 mins ago 0x4bf55262c17388c13cdd9538a830b32191493667 0xb9713d033df6190d941f169cdedc1c69b5314e720 Ether
0x661f74c00363f04c8acb143384964c344afd5913263fbfa7cbf880773d70e53d325356132022-07-04 4:58:5219 hrs 24 mins ago 0xb9713d033df6190d941f169cdedc1c69b5314e72 0xb1751e5ede811288ce2fc4c65aaca17a955366be0 Ether
0x661f74c00363f04c8acb143384964c344afd5913263fbfa7cbf880773d70e53d325356132022-07-04 4:58:5219 hrs 24 mins ago 0xb9713d033df6190d941f169cdedc1c69b5314e72 0xa3f59b8e28cabc4411198dda2e65c380bd5d6dfe0 Ether
0x661f74c00363f04c8acb143384964c344afd5913263fbfa7cbf880773d70e53d325356132022-07-04 4:58:5219 hrs 24 mins ago 0x4bf55262c17388c13cdd9538a830b32191493667 0xb9713d033df6190d941f169cdedc1c69b5314e720 Ether
0x27faf492a0a6aaca17c0d0f68adb4088f325a9f2887f4a6ae94e3aada6849b48325355962022-07-04 4:57:2419 hrs 25 mins ago 0xb9713d033df6190d941f169cdedc1c69b5314e72 0xa3f59b8e28cabc4411198dda2e65c380bd5d6dfe0 Ether
0x27faf492a0a6aaca17c0d0f68adb4088f325a9f2887f4a6ae94e3aada6849b48325355962022-07-04 4:57:2419 hrs 25 mins ago 0x4bf55262c17388c13cdd9538a830b32191493667 0xb9713d033df6190d941f169cdedc1c69b5314e720 Ether
0x27faf492a0a6aaca17c0d0f68adb4088f325a9f2887f4a6ae94e3aada6849b48325355962022-07-04 4:57:2419 hrs 25 mins ago 0xb9713d033df6190d941f169cdedc1c69b5314e72 0xb1751e5ede811288ce2fc4c65aaca17a955366be0 Ether
0x27faf492a0a6aaca17c0d0f68adb4088f325a9f2887f4a6ae94e3aada6849b48325355962022-07-04 4:57:2419 hrs 25 mins ago 0xb9713d033df6190d941f169cdedc1c69b5314e72 0xa3f59b8e28cabc4411198dda2e65c380bd5d6dfe0 Ether
0x27faf492a0a6aaca17c0d0f68adb4088f325a9f2887f4a6ae94e3aada6849b48325355962022-07-04 4:57:2419 hrs 25 mins ago 0x4bf55262c17388c13cdd9538a830b32191493667 0xb9713d033df6190d941f169cdedc1c69b5314e720 Ether
0x8f2750a1aa80d64cbee291f3c86c2e7d7426c95fe996105830adc4a07d508974325023262022-07-02 6:23:562 days 17 hrs ago 0xb9713d033df6190d941f169cdedc1c69b5314e72 0xa3f59b8e28cabc4411198dda2e65c380bd5d6dfe0 Ether
0x8f2750a1aa80d64cbee291f3c86c2e7d7426c95fe996105830adc4a07d508974325023262022-07-02 6:23:562 days 17 hrs ago 0x4bf55262c17388c13cdd9538a830b32191493667 0xb9713d033df6190d941f169cdedc1c69b5314e720 Ether
0x8f2750a1aa80d64cbee291f3c86c2e7d7426c95fe996105830adc4a07d508974325023262022-07-02 6:23:562 days 17 hrs ago 0xb9713d033df6190d941f169cdedc1c69b5314e72 0xb1751e5ede811288ce2fc4c65aaca17a955366be0 Ether
0x8f2750a1aa80d64cbee291f3c86c2e7d7426c95fe996105830adc4a07d508974325023262022-07-02 6:23:562 days 17 hrs ago 0xb9713d033df6190d941f169cdedc1c69b5314e72 0xa3f59b8e28cabc4411198dda2e65c380bd5d6dfe0 Ether
0x8f2750a1aa80d64cbee291f3c86c2e7d7426c95fe996105830adc4a07d508974325023262022-07-02 6:23:562 days 17 hrs ago 0x4bf55262c17388c13cdd9538a830b32191493667 0xb9713d033df6190d941f169cdedc1c69b5314e720 Ether
0x8f2750a1aa80d64cbee291f3c86c2e7d7426c95fe996105830adc4a07d508974325023262022-07-02 6:23:562 days 17 hrs ago 0xb9713d033df6190d941f169cdedc1c69b5314e72 0xa3f59b8e28cabc4411198dda2e65c380bd5d6dfe0 Ether
0x8f2750a1aa80d64cbee291f3c86c2e7d7426c95fe996105830adc4a07d508974325023262022-07-02 6:23:562 days 17 hrs ago 0x4bf55262c17388c13cdd9538a830b32191493667 0xb9713d033df6190d941f169cdedc1c69b5314e720 Ether
0x8f2750a1aa80d64cbee291f3c86c2e7d7426c95fe996105830adc4a07d508974325023262022-07-02 6:23:562 days 17 hrs ago 0xb9713d033df6190d941f169cdedc1c69b5314e72 0xb1751e5ede811288ce2fc4c65aaca17a955366be0 Ether
0x8f2750a1aa80d64cbee291f3c86c2e7d7426c95fe996105830adc4a07d508974325023262022-07-02 6:23:562 days 17 hrs ago 0xb9713d033df6190d941f169cdedc1c69b5314e72 0xa3f59b8e28cabc4411198dda2e65c380bd5d6dfe0 Ether
0x8f2750a1aa80d64cbee291f3c86c2e7d7426c95fe996105830adc4a07d508974325023262022-07-02 6:23:562 days 17 hrs ago 0x4bf55262c17388c13cdd9538a830b32191493667 0xb9713d033df6190d941f169cdedc1c69b5314e720 Ether
[ Download CSV Export 
Loading
This contract may be a proxy contract. Click on More Options and select Is this a proxy? to confirm and enable the "Read as Proxy" & "Write as Proxy" tabs.

Contract Source Code Verified (Exact Match)

Contract Name:
ExchangerWithFeeRecAlternatives

Compiler Version
v0.5.16+commit.9c3226ce

Optimization Enabled:
Yes with 200 runs

Other Settings:
default evmVersion

Contract Source Code (Solidity)

/**
 *Submitted for verification at Etherscan.io on 2022-05-13
*/

/*
   ____            __   __        __   _
  / __/__ __ ___  / /_ / /  ___  / /_ (_)__ __
 _\ \ / // // _ \/ __// _ \/ -_)/ __// / \ \ /
/___/ \_, //_//_/\__//_//_/\__/ \__//_/ /_\_\
     /___/

* Synthetix: ExchangerWithFeeRecAlternatives.sol
*
* Latest source (may be newer): https://github.com/Synthetixio/synthetix/blob/master/contracts/ExchangerWithFeeRecAlternatives.sol
* Docs: https://docs.synthetix.io/contracts/ExchangerWithFeeRecAlternatives
*
* Contract Dependencies: 
*	- Exchanger
*	- IAddressResolver
*	- IExchanger
*	- MinimalProxyFactory
*	- MixinResolver
*	- MixinSystemSettings
*	- Owned
* Libraries: 
*	- SafeDecimalMath
*	- SafeMath
*
* MIT License
* ===========
*
* Copyright (c) 2022 Synthetix
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
*/



pragma solidity ^0.5.16;

// https://docs.synthetix.io/contracts/source/contracts/owned
contract Owned {
    address public owner;
    address public nominatedOwner;

    constructor(address _owner) public {
        require(_owner != address(0), "Owner address cannot be 0");
        owner = _owner;
        emit OwnerChanged(address(0), _owner);
    }

    function nominateNewOwner(address _owner) external onlyOwner {
        nominatedOwner = _owner;
        emit OwnerNominated(_owner);
    }

    function acceptOwnership() external {
        require(msg.sender == nominatedOwner, "You must be nominated before you can accept ownership");
        emit OwnerChanged(owner, nominatedOwner);
        owner = nominatedOwner;
        nominatedOwner = address(0);
    }

    modifier onlyOwner {
        _onlyOwner();
        _;
    }

    function _onlyOwner() private view {
        require(msg.sender == owner, "Only the contract owner may perform this action");
    }

    event OwnerNominated(address newOwner);
    event OwnerChanged(address oldOwner, address newOwner);
}


// 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);
}


// 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;
}


// 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);

    // Restricted: used internally to Synthetix
    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 amountToLiquidate);

    function issueSynthsWithoutDebt(
        bytes32 currencyKey,
        address to,
        uint amount
    ) external returns (bool rateInvalid);

    function burnSynthsWithoutDebt(
        bytes32 currencyKey,
        address to,
        uint amount
    ) external returns (bool rateInvalid);
}


// Inheritance


// Internal references


// 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);
}


// Internal references


// https://docs.synthetix.io/contracts/source/contracts/mixinresolver
contract MixinResolver {
    AddressResolver public resolver;

    mapping(bytes32 => address) private addressCache;

    constructor(address _resolver) internal {
        resolver = AddressResolver(_resolver);
    }

    /* ========== INTERNAL FUNCTIONS ========== */

    function combineArrays(bytes32[] memory first, bytes32[] memory second)
        internal
        pure
        returns (bytes32[] memory combination)
    {
        combination = new bytes32[](first.length + second.length);

        for (uint i = 0; i < first.length; i++) {
            combination[i] = first[i];
        }

        for (uint j = 0; j < second.length; j++) {
            combination[first.length + j] = second[j];
        }
    }

    /* ========== PUBLIC FUNCTIONS ========== */

    // Note: this function is public not external in order for it to be overridden and invoked via super in subclasses
    function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {}

    function rebuildCache() public {
        bytes32[] memory requiredAddresses = resolverAddressesRequired();
        // The resolver must call this function whenver it updates its state
        for (uint i = 0; i < requiredAddresses.length; i++) {
            bytes32 name = requiredAddresses[i];
            // Note: can only be invoked once the resolver has all the targets needed added
            address destination =
                resolver.requireAndGetAddress(name, string(abi.encodePacked("Resolver missing target: ", name)));
            addressCache[name] = destination;
            emit CacheUpdated(name, destination);
        }
    }

    /* ========== VIEWS ========== */

    function isResolverCached() external view returns (bool) {
        bytes32[] memory requiredAddresses = resolverAddressesRequired();
        for (uint i = 0; i < requiredAddresses.length; i++) {
            bytes32 name = requiredAddresses[i];
            // false if our cache is invalid or if the resolver doesn't have the required address
            if (resolver.getAddress(name) != addressCache[name] || addressCache[name] == address(0)) {
                return false;
            }
        }

        return true;
    }

    /* ========== INTERNAL FUNCTIONS ========== */

    function requireAndGetAddress(bytes32 name) internal view returns (address) {
        address _foundAddress = addressCache[name];
        require(_foundAddress != address(0), string(abi.encodePacked("Missing address: ", name)));
        return _foundAddress;
    }

    /* ========== EVENTS ========== */

    event CacheUpdated(bytes32 name, address destination);
}


// 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;
}


// Internal references


// https://docs.synthetix.io/contracts/source/contracts/mixinsystemsettings
contract MixinSystemSettings is MixinResolver {
    // must match the one defined SystemSettingsLib, defined in both places due to sol v0.5 limitations
    bytes32 internal constant SETTING_CONTRACT_NAME = "SystemSettings";

    bytes32 internal constant SETTING_WAITING_PERIOD_SECS = "waitingPeriodSecs";
    bytes32 internal constant SETTING_PRICE_DEVIATION_THRESHOLD_FACTOR = "priceDeviationThresholdFactor";
    bytes32 internal constant SETTING_ISSUANCE_RATIO = "issuanceRatio";
    bytes32 internal constant SETTING_FEE_PERIOD_DURATION = "feePeriodDuration";
    bytes32 internal constant SETTING_TARGET_THRESHOLD = "targetThreshold";
    bytes32 internal constant SETTING_LIQUIDATION_DELAY = "liquidationDelay";
    bytes32 internal constant SETTING_LIQUIDATION_RATIO = "liquidationRatio";
    bytes32 internal constant SETTING_LIQUIDATION_ESCROW_DURATION = "liquidationEscrowDuration";
    bytes32 internal constant SETTING_LIQUIDATION_PENALTY = "liquidationPenalty";
    bytes32 internal constant SETTING_SELF_LIQUIDATION_PENALTY = "selfLiquidationPenalty";
    bytes32 internal constant SETTING_FLAG_REWARD = "flagReward";
    bytes32 internal constant SETTING_LIQUIDATE_REWARD = "liquidateReward";
    bytes32 internal constant SETTING_RATE_STALE_PERIOD = "rateStalePeriod";
    /* ========== Exchange Fees Related ========== */
    bytes32 internal constant SETTING_EXCHANGE_FEE_RATE = "exchangeFeeRate";
    bytes32 internal constant SETTING_EXCHANGE_DYNAMIC_FEE_THRESHOLD = "exchangeDynamicFeeThreshold";
    bytes32 internal constant SETTING_EXCHANGE_DYNAMIC_FEE_WEIGHT_DECAY = "exchangeDynamicFeeWeightDecay";
    bytes32 internal constant SETTING_EXCHANGE_DYNAMIC_FEE_ROUNDS = "exchangeDynamicFeeRounds";
    bytes32 internal constant SETTING_EXCHANGE_MAX_DYNAMIC_FEE = "exchangeMaxDynamicFee";
    /* ========== End Exchange Fees Related ========== */
    bytes32 internal constant SETTING_MINIMUM_STAKE_TIME = "minimumStakeTime";
    bytes32 internal constant SETTING_AGGREGATOR_WARNING_FLAGS = "aggregatorWarningFlags";
    bytes32 internal constant SETTING_TRADING_REWARDS_ENABLED = "tradingRewardsEnabled";
    bytes32 internal constant SETTING_DEBT_SNAPSHOT_STALE_TIME = "debtSnapshotStaleTime";
    bytes32 internal constant SETTING_CROSS_DOMAIN_DEPOSIT_GAS_LIMIT = "crossDomainDepositGasLimit";
    bytes32 internal constant SETTING_CROSS_DOMAIN_ESCROW_GAS_LIMIT = "crossDomainEscrowGasLimit";
    bytes32 internal constant SETTING_CROSS_DOMAIN_REWARD_GAS_LIMIT = "crossDomainRewardGasLimit";
    bytes32 internal constant SETTING_CROSS_DOMAIN_WITHDRAWAL_GAS_LIMIT = "crossDomainWithdrawalGasLimit";
    bytes32 internal constant SETTING_CROSS_DOMAIN_FEE_PERIOD_CLOSE_GAS_LIMIT = "crossDomainCloseGasLimit";
    bytes32 internal constant SETTING_CROSS_DOMAIN_RELAY_GAS_LIMIT = "crossDomainRelayGasLimit";
    bytes32 internal constant SETTING_ETHER_WRAPPER_MAX_ETH = "etherWrapperMaxETH";
    bytes32 internal constant SETTING_ETHER_WRAPPER_MINT_FEE_RATE = "etherWrapperMintFeeRate";
    bytes32 internal constant SETTING_ETHER_WRAPPER_BURN_FEE_RATE = "etherWrapperBurnFeeRate";
    bytes32 internal constant SETTING_WRAPPER_MAX_TOKEN_AMOUNT = "wrapperMaxTokens";
    bytes32 internal constant SETTING_WRAPPER_MINT_FEE_RATE = "wrapperMintFeeRate";
    bytes32 internal constant SETTING_WRAPPER_BURN_FEE_RATE = "wrapperBurnFeeRate";
    bytes32 internal constant SETTING_INTERACTION_DELAY = "interactionDelay";
    bytes32 internal constant SETTING_COLLAPSE_FEE_RATE = "collapseFeeRate";
    bytes32 internal constant SETTING_ATOMIC_MAX_VOLUME_PER_BLOCK = "atomicMaxVolumePerBlock";
    bytes32 internal constant SETTING_ATOMIC_TWAP_WINDOW = "atomicTwapWindow";
    bytes32 internal constant SETTING_ATOMIC_EQUIVALENT_FOR_DEX_PRICING = "atomicEquivalentForDexPricing";
    bytes32 internal constant SETTING_ATOMIC_EXCHANGE_FEE_RATE = "atomicExchangeFeeRate";
    bytes32 internal constant SETTING_ATOMIC_VOLATILITY_CONSIDERATION_WINDOW = "atomicVolConsiderationWindow";
    bytes32 internal constant SETTING_ATOMIC_VOLATILITY_UPDATE_THRESHOLD = "atomicVolUpdateThreshold";
    bytes32 internal constant SETTING_PURE_CHAINLINK_PRICE_FOR_ATOMIC_SWAPS_ENABLED = "pureChainlinkForAtomicsEnabled";
    bytes32 internal constant SETTING_CROSS_SYNTH_TRANSFER_ENABLED = "crossChainSynthTransferEnabled";

    bytes32 internal constant CONTRACT_FLEXIBLESTORAGE = "FlexibleStorage";

    enum CrossDomainMessageGasLimits {Deposit, Escrow, Reward, Withdrawal, CloseFeePeriod, Relay}

    struct DynamicFeeConfig {
        uint threshold;
        uint weightDecay;
        uint rounds;
        uint maxFee;
    }

    constructor(address _resolver) internal MixinResolver(_resolver) {}

    function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
        addresses = new bytes32[](1);
        addresses[0] = CONTRACT_FLEXIBLESTORAGE;
    }

    function flexibleStorage() internal view returns (IFlexibleStorage) {
        return IFlexibleStorage(requireAndGetAddress(CONTRACT_FLEXIBLESTORAGE));
    }

    function _getGasLimitSetting(CrossDomainMessageGasLimits gasLimitType) internal pure returns (bytes32) {
        if (gasLimitType == CrossDomainMessageGasLimits.Deposit) {
            return SETTING_CROSS_DOMAIN_DEPOSIT_GAS_LIMIT;
        } else if (gasLimitType == CrossDomainMessageGasLimits.Escrow) {
            return SETTING_CROSS_DOMAIN_ESCROW_GAS_LIMIT;
        } else if (gasLimitType == CrossDomainMessageGasLimits.Reward) {
            return SETTING_CROSS_DOMAIN_REWARD_GAS_LIMIT;
        } else if (gasLimitType == CrossDomainMessageGasLimits.Withdrawal) {
            return SETTING_CROSS_DOMAIN_WITHDRAWAL_GAS_LIMIT;
        } else if (gasLimitType == CrossDomainMessageGasLimits.Relay) {
            return SETTING_CROSS_DOMAIN_RELAY_GAS_LIMIT;
        } else if (gasLimitType == CrossDomainMessageGasLimits.CloseFeePeriod) {
            return SETTING_CROSS_DOMAIN_FEE_PERIOD_CLOSE_GAS_LIMIT;
        } else {
            revert("Unknown gas limit type");
        }
    }

    function getCrossDomainMessageGasLimit(CrossDomainMessageGasLimits gasLimitType) internal view returns (uint) {
        return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, _getGasLimitSetting(gasLimitType));
    }

    function getTradingRewardsEnabled() internal view returns (bool) {
        return flexibleStorage().getBoolValue(SETTING_CONTRACT_NAME, SETTING_TRADING_REWARDS_ENABLED);
    }

    function getWaitingPeriodSecs() internal view returns (uint) {
        return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_WAITING_PERIOD_SECS);
    }

    function getPriceDeviationThresholdFactor() internal view returns (uint) {
        return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_PRICE_DEVIATION_THRESHOLD_FACTOR);
    }

    function getIssuanceRatio() internal view returns (uint) {
        // lookup on flexible storage directly for gas savings (rather than via SystemSettings)
        return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_ISSUANCE_RATIO);
    }

    function getFeePeriodDuration() internal view returns (uint) {
        // lookup on flexible storage directly for gas savings (rather than via SystemSettings)
        return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_FEE_PERIOD_DURATION);
    }

    function getTargetThreshold() internal view returns (uint) {
        // lookup on flexible storage directly for gas savings (rather than via SystemSettings)
        return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_TARGET_THRESHOLD);
    }

    function getLiquidationDelay() internal view returns (uint) {
        return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_LIQUIDATION_DELAY);
    }

    function getLiquidationRatio() internal view returns (uint) {
        return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_LIQUIDATION_RATIO);
    }

    function getLiquidationEscrowDuration() internal view returns (uint) {
        return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_LIQUIDATION_ESCROW_DURATION);
    }

    function getLiquidationPenalty() internal view returns (uint) {
        return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_LIQUIDATION_PENALTY);
    }

    function getSelfLiquidationPenalty() internal view returns (uint) {
        return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_SELF_LIQUIDATION_PENALTY);
    }

    function getFlagReward() internal view returns (uint) {
        return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_FLAG_REWARD);
    }

    function getLiquidateReward() internal view returns (uint) {
        return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_LIQUIDATE_REWARD);
    }

    function getRateStalePeriod() internal view returns (uint) {
        return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_RATE_STALE_PERIOD);
    }

    /* ========== Exchange Related Fees ========== */
    function getExchangeFeeRate(bytes32 currencyKey) internal view returns (uint) {
        return
            flexibleStorage().getUIntValue(
                SETTING_CONTRACT_NAME,
                keccak256(abi.encodePacked(SETTING_EXCHANGE_FEE_RATE, currencyKey))
            );
    }

    /// @notice Get exchange dynamic fee related keys
    /// @return threshold, weight decay, rounds, and max fee
    function getExchangeDynamicFeeConfig() internal view returns (DynamicFeeConfig memory) {
        bytes32[] memory keys = new bytes32[](4);
        keys[0] = SETTING_EXCHANGE_DYNAMIC_FEE_THRESHOLD;
        keys[1] = SETTING_EXCHANGE_DYNAMIC_FEE_WEIGHT_DECAY;
        keys[2] = SETTING_EXCHANGE_DYNAMIC_FEE_ROUNDS;
        keys[3] = SETTING_EXCHANGE_MAX_DYNAMIC_FEE;
        uint[] memory values = flexibleStorage().getUIntValues(SETTING_CONTRACT_NAME, keys);
        return DynamicFeeConfig({threshold: values[0], weightDecay: values[1], rounds: values[2], maxFee: values[3]});
    }

    /* ========== End Exchange Related Fees ========== */

    function getMinimumStakeTime() internal view returns (uint) {
        return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_MINIMUM_STAKE_TIME);
    }

    function getAggregatorWarningFlags() internal view returns (address) {
        return flexibleStorage().getAddressValue(SETTING_CONTRACT_NAME, SETTING_AGGREGATOR_WARNING_FLAGS);
    }

    function getDebtSnapshotStaleTime() internal view returns (uint) {
        return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_DEBT_SNAPSHOT_STALE_TIME);
    }

    function getEtherWrapperMaxETH() internal view returns (uint) {
        return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_ETHER_WRAPPER_MAX_ETH);
    }

    function getEtherWrapperMintFeeRate() internal view returns (uint) {
        return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_ETHER_WRAPPER_MINT_FEE_RATE);
    }

    function getEtherWrapperBurnFeeRate() internal view returns (uint) {
        return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_ETHER_WRAPPER_BURN_FEE_RATE);
    }

    function getWrapperMaxTokenAmount(address wrapper) internal view returns (uint) {
        return
            flexibleStorage().getUIntValue(
                SETTING_CONTRACT_NAME,
                keccak256(abi.encodePacked(SETTING_WRAPPER_MAX_TOKEN_AMOUNT, wrapper))
            );
    }

    function getWrapperMintFeeRate(address wrapper) internal view returns (int) {
        return
            flexibleStorage().getIntValue(
                SETTING_CONTRACT_NAME,
                keccak256(abi.encodePacked(SETTING_WRAPPER_MINT_FEE_RATE, wrapper))
            );
    }

    function getWrapperBurnFeeRate(address wrapper) internal view returns (int) {
        return
            flexibleStorage().getIntValue(
                SETTING_CONTRACT_NAME,
                keccak256(abi.encodePacked(SETTING_WRAPPER_BURN_FEE_RATE, wrapper))
            );
    }

    function getInteractionDelay(address collateral) internal view returns (uint) {
        return
            flexibleStorage().getUIntValue(
                SETTING_CONTRACT_NAME,
                keccak256(abi.encodePacked(SETTING_INTERACTION_DELAY, collateral))
            );
    }

    function getCollapseFeeRate(address collateral) internal view returns (uint) {
        return
            flexibleStorage().getUIntValue(
                SETTING_CONTRACT_NAME,
                keccak256(abi.encodePacked(SETTING_COLLAPSE_FEE_RATE, collateral))
            );
    }

    function getAtomicMaxVolumePerBlock() internal view returns (uint) {
        return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_ATOMIC_MAX_VOLUME_PER_BLOCK);
    }

    function getAtomicTwapWindow() internal view returns (uint) {
        return flexibleStorage().getUIntValue(SETTING_CONTRACT_NAME, SETTING_ATOMIC_TWAP_WINDOW);
    }

    function getAtomicEquivalentForDexPricing(bytes32 currencyKey) internal view returns (address) {
        return
            flexibleStorage().getAddressValue(
                SETTING_CONTRACT_NAME,
                keccak256(abi.encodePacked(SETTING_ATOMIC_EQUIVALENT_FOR_DEX_PRICING, currencyKey))
            );
    }

    function getAtomicExchangeFeeRate(bytes32 currencyKey) internal view returns (uint) {
        return
            flexibleStorage().getUIntValue(
                SETTING_CONTRACT_NAME,
                keccak256(abi.encodePacked(SETTING_ATOMIC_EXCHANGE_FEE_RATE, currencyKey))
            );
    }

    function getAtomicVolatilityConsiderationWindow(bytes32 currencyKey) internal view returns (uint) {
        return
            flexibleStorage().getUIntValue(
                SETTING_CONTRACT_NAME,
                keccak256(abi.encodePacked(SETTING_ATOMIC_VOLATILITY_CONSIDERATION_WINDOW, currencyKey))
            );
    }

    function getAtomicVolatilityUpdateThreshold(bytes32 currencyKey) internal view returns (uint) {
        return
            flexibleStorage().getUIntValue(
                SETTING_CONTRACT_NAME,
                keccak256(abi.encodePacked(SETTING_ATOMIC_VOLATILITY_UPDATE_THRESHOLD, currencyKey))
            );
    }

    function getPureChainlinkPriceForAtomicSwapsEnabled(bytes32 currencyKey) internal view returns (bool) {
        return
            flexibleStorage().getBoolValue(
                SETTING_CONTRACT_NAME,
                keccak256(abi.encodePacked(SETTING_PURE_CHAINLINK_PRICE_FOR_ATOMIC_SWAPS_ENABLED, currencyKey))
            );
    }

    function getCrossChainSynthTransferEnabled(bytes32 currencyKey) internal view returns (uint) {
        return
            flexibleStorage().getUIntValue(
                SETTING_CONTRACT_NAME,
                keccak256(abi.encodePacked(SETTING_CROSS_SYNTH_TRANSFER_ENABLED, currencyKey))
            );
    }
}


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;
}


// 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;
    }

    // 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
        );

    function suspendSynthWithInvalidRate(bytes32 currencyKey) external;
}


/**
 * @dev Wrappers over Solidity's arithmetic operations with added overflow
 * checks.
 *
 * Arithmetic operations in Solidity wrap on overflow. This can easily result
 * in bugs, because programmers usually assume that an overflow raises an
 * error, which is the standard behavior in high level programming languages.
 * `SafeMath` restores this intuition by reverting the transaction when an
 * operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 */
library SafeMath {
    /**
     * @dev Returns the addition of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");

        return c;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b <= a, "SafeMath: subtraction overflow");
        uint256 c = a - b;

        return c;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
        if (a == 0) {
            return 0;
        }

        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");

        return c;
    }

    /**
     * @dev Returns the integer division of two unsigned integers. Reverts on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        // Solidity only automatically asserts when dividing by 0
        require(b > 0, "SafeMath: division by zero");
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold

        return c;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * Reverts when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b != 0, "SafeMath: modulo by zero");
        return a % b;
    }
}


// Libraries


// https://docs.synthetix.io/contracts/source/libraries/safedecimalmath
library SafeDecimalMath {
    using SafeMath for uint;

    /* Number of decimal places in the representations. */
    uint8 public constant decimals = 18;
    uint8 public constant highPrecisionDecimals = 27;

    /* The number representing 1.0. */
    uint public constant UNIT = 10**uint(decimals);

    /* The number representing 1.0 for higher fidelity numbers. */
    uint public constant PRECISE_UNIT = 10**uint(highPrecisionDecimals);
    uint private constant UNIT_TO_HIGH_PRECISION_CONVERSION_FACTOR = 10**uint(highPrecisionDecimals - decimals);

    /**
     * @return Provides an interface to UNIT.
     */
    function unit() external pure returns (uint) {
        return UNIT;
    }

    /**
     * @return Provides an interface to PRECISE_UNIT.
     */
    function preciseUnit() external pure returns (uint) {
        return PRECISE_UNIT;
    }

    /**
     * @return The result of multiplying x and y, interpreting the operands as fixed-point
     * decimals.
     *
     * @dev A unit factor is divided out after the product of x and y is evaluated,
     * so that product must be less than 2**256. As this is an integer division,
     * the internal division always rounds down. This helps save on gas. Rounding
     * is more expensive on gas.
     */
    function multiplyDecimal(uint x, uint y) internal pure returns (uint) {
        /* Divide by UNIT to remove the extra factor introduced by the product. */
        return x.mul(y) / UNIT;
    }

    /**
     * @return The result of safely multiplying x and y, interpreting the operands
     * as fixed-point decimals of the specified precision unit.
     *
     * @dev The operands should be in the form of a the specified unit factor which will be
     * divided out after the product of x and y is evaluated, so that product must be
     * less than 2**256.
     *
     * Unlike multiplyDecimal, this function rounds the result to the nearest increment.
     * Rounding is useful when you need to retain fidelity for small decimal numbers
     * (eg. small fractions or percentages).
     */
    function _multiplyDecimalRound(
        uint x,
        uint y,
        uint precisionUnit
    ) private pure returns (uint) {
        /* Divide by UNIT to remove the extra factor introduced by the product. */
        uint quotientTimesTen = x.mul(y) / (precisionUnit / 10);

        if (quotientTimesTen % 10 >= 5) {
            quotientTimesTen += 10;
        }

        return quotientTimesTen / 10;
    }

    /**
     * @return The result of safely multiplying x and y, interpreting the operands
     * as fixed-point decimals of a precise unit.
     *
     * @dev The operands should be in the precise unit factor which will be
     * divided out after the product of x and y is evaluated, so that product must be
     * less than 2**256.
     *
     * Unlike multiplyDecimal, this function rounds the result to the nearest increment.
     * Rounding is useful when you need to retain fidelity for small decimal numbers
     * (eg. small fractions or percentages).
     */
    function multiplyDecimalRoundPrecise(uint x, uint y) internal pure returns (uint) {
        return _multiplyDecimalRound(x, y, PRECISE_UNIT);
    }

    /**
     * @return The result of safely multiplying x and y, interpreting the operands
     * as fixed-point decimals of a standard unit.
     *
     * @dev The operands should be in the standard unit factor which will be
     * divided out after the product of x and y is evaluated, so that product must be
     * less than 2**256.
     *
     * Unlike multiplyDecimal, this function rounds the result to the nearest increment.
     * Rounding is useful when you need to retain fidelity for small decimal numbers
     * (eg. small fractions or percentages).
     */
    function multiplyDecimalRound(uint x, uint y) internal pure returns (uint) {
        return _multiplyDecimalRound(x, y, UNIT);
    }

    /**
     * @return The result of safely dividing x and y. The return value is a high
     * precision decimal.
     *
     * @dev y is divided after the product of x and the standard precision unit
     * is evaluated, so the product of x and UNIT must be less than 2**256. As
     * this is an integer division, the result is always rounded down.
     * This helps save on gas. Rounding is more expensive on gas.
     */
    function divideDecimal(uint x, uint y) internal pure returns (uint) {
        /* Reintroduce the UNIT factor that will be divided out by y. */
        return x.mul(UNIT).div(y);
    }

    /**
     * @return The result of safely dividing x and y. The return value is as a rounded
     * decimal in the precision unit specified in the parameter.
     *
     * @dev y is divided after the product of x and the specified precision unit
     * is evaluated, so the product of x and the specified precision unit must
     * be less than 2**256. The result is rounded to the nearest increment.
     */
    function _divideDecimalRound(
        uint x,
        uint y,
        uint precisionUnit
    ) private pure returns (uint) {
        uint resultTimesTen = x.mul(precisionUnit * 10).div(y);

        if (resultTimesTen % 10 >= 5) {
            resultTimesTen += 10;
        }

        return resultTimesTen / 10;
    }

    /**
     * @return The result of safely dividing x and y. The return value is as a rounded
     * standard precision decimal.
     *
     * @dev y is divided after the product of x and the standard precision unit
     * is evaluated, so the product of x and the standard precision unit must
     * be less than 2**256. The result is rounded to the nearest increment.
     */
    function divideDecimalRound(uint x, uint y) internal pure returns (uint) {
        return _divideDecimalRound(x, y, UNIT);
    }

    /**
     * @return The result of safely dividing x and y. The return value is as a rounded
     * high precision decimal.
     *
     * @dev y is divided after the product of x and the high precision unit
     * is evaluated, so the product of x and the high precision unit must
     * be less than 2**256. The result is rounded to the nearest increment.
     */
    function divideDecimalRoundPrecise(uint x, uint y) internal pure returns (uint) {
        return _divideDecimalRound(x, y, PRECISE_UNIT);
    }

    /**
     * @dev Convert a standard decimal representation to a high precision one.
     */
    function decimalToPreciseDecimal(uint i) internal pure returns (uint) {
        return i.mul(UNIT_TO_HIGH_PRECISION_CONVERSION_FACTOR);
    }

    /**
     * @dev Convert a high precision decimal to a standard decimal representation.
     */
    function preciseDecimalToDecimal(uint i) internal pure returns (uint) {
        uint quotientTimesTen = i / (UNIT_TO_HIGH_PRECISION_CONVERSION_FACTOR / 10);

        if (quotientTimesTen % 10 >= 5) {
            quotientTimesTen += 10;
        }

        return quotientTimesTen / 10;
    }

    // Computes `a - b`, setting the value to 0 if b > a.
    function floorsub(uint a, uint b) internal pure returns (uint) {
        return b >= a ? 0 : a - b;
    }

    /* ---------- 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));
    }
}


// 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;
}


// https://docs.synthetix.io/contracts/source/interfaces/ierc20
interface IERC20 {
    // 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);

    // Events
    event Transfer(address indexed from, address indexed to, uint value);

    event Approval(address indexed owner, address indexed spender, uint value);
}


// 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;
}


// 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 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);
}


// https://docs.synthetix.io/contracts/source/interfaces/IExchangeCircuitBreaker
interface IExchangeCircuitBreaker {
    // Views

    function exchangeRates() external view returns (address);

    function rateWithInvalid(bytes32 currencyKey) external view returns (uint, bool);

    function priceDeviationThresholdFactor() external view returns (uint);

    function isDeviationAboveThreshold(uint base, uint comparison) external view returns (bool);

    function lastExchangeRate(bytes32 currencyKey) external view returns (uint);

    // Mutative functions
    function resetLastExchangeRate(bytes32[] calldata currencyKeys) external;

    function rateWithBreakCircuit(bytes32 currencyKey) external returns (uint lastValidRate, bool circuitBroken);
}


// 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);

    // 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 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;
}


// 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 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 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;
}


// 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;
}


// 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;
}


// Inheritance


// Internal references


// https://docs.synthetix.io/contracts/source/contracts/proxy
contract Proxy is Owned {
    Proxyable public target;

    constructor(address _owner) public Owned(_owner) {}

    function setTarget(Proxyable _target) external onlyOwner {
        target = _target;
        emit TargetUpdated(_target);
    }

    function _emit(
        bytes calldata callData,
        uint numTopics,
        bytes32 topic1,
        bytes32 topic2,
        bytes32 topic3,
        bytes32 topic4
    ) external onlyTarget {
        uint size = callData.length;
        bytes memory _callData = callData;

        assembly {
            /* The first 32 bytes of callData contain its length (as specified by the abi).
             * Length is assumed to be a uint256 and therefore maximum of 32 bytes
             * in length. It is also leftpadded to be a multiple of 32 bytes.
             * This means moving call_data across 32 bytes guarantees we correctly access
             * the data itself. */
            switch numTopics
                case 0 {
                    log0(add(_callData, 32), size)
                }
                case 1 {
                    log1(add(_callData, 32), size, topic1)
                }
                case 2 {
                    log2(add(_callData, 32), size, topic1, topic2)
                }
                case 3 {
                    log3(add(_callData, 32), size, topic1, topic2, topic3)
                }
                case 4 {
                    log4(add(_callData, 32), size, topic1, topic2, topic3, topic4)
                }
        }
    }

    // solhint-disable no-complex-fallback
    function() external payable {
        // Mutable call setting Proxyable.messageSender as this is using call not delegatecall
        target.setMessageSender(msg.sender);

        assembly {
            let free_ptr := mload(0x40)
            calldatacopy(free_ptr, 0, calldatasize)

            /* We must explicitly forward ether to the underlying contract as well. */
            let result := call(gas, sload(target_slot), callvalue, free_ptr, calldatasize, 0, 0)
            returndatacopy(free_ptr, 0, returndatasize)

            if iszero(result) {
                revert(free_ptr, returndatasize)
            }
            return(free_ptr, returndatasize)
        }
    }

    modifier onlyTarget {
        require(Proxyable(msg.sender) == target, "Must be proxy target");
        _;
    }

    event TargetUpdated(Proxyable newTarget);
}


// Inheritance


// Internal references


// https://docs.synthetix.io/contracts/source/contracts/proxyable
contract Proxyable is Owned {
    // This contract should be treated like an abstract contract

    /* The proxy this contract exists behind. */
    Proxy public proxy;

    /* The caller of the proxy, passed through to this contract.
     * Note that every function using this member must apply the onlyProxy or
     * optionalProxy modifiers, otherwise their invocations can use stale values. */
    address public messageSender;

    constructor(address payable _proxy) internal {
        // This contract is abstract, and thus cannot be instantiated directly
        require(owner != address(0), "Owner must be set");

        proxy = Proxy(_proxy);
        emit ProxyUpdated(_proxy);
    }

    function setProxy(address payable _proxy) external onlyOwner {
        proxy = Proxy(_proxy);
        emit ProxyUpdated(_proxy);
    }

    function setMessageSender(address sender) external onlyProxy {
        messageSender = sender;
    }

    modifier onlyProxy {
        _onlyProxy();
        _;
    }

    function _onlyProxy() private view {
        require(Proxy(msg.sender) == proxy, "Only the proxy can call");
    }

    modifier optionalProxy {
        _optionalProxy();
        _;
    }

    function _optionalProxy() private {
        if (Proxy(msg.sender) != proxy && messageSender != msg.sender) {
            messageSender = msg.sender;
        }
    }

    modifier optionalProxy_onlyOwner {
        _optionalProxy_onlyOwner();
        _;
    }

    // solhint-disable-next-line func-name-mixedcase
    function _optionalProxy_onlyOwner() private {
        if (Proxy(msg.sender) != proxy && messageSender != msg.sender) {
            messageSender = msg.sender;
        }
        require(messageSender == owner, "Owner only function");
    }

    event ProxyUpdated(address proxyAddress);
}


// Inheritance


// Libraries


// Internal references


// 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;
}

// 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 = "ExchangeCircuitBreaker";

    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[](10);
        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;
        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 exchangeCircuitBreaker() internal view returns (IExchangeCircuitBreaker) {
        return IExchangeCircuitBreaker(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 maxSecsLeftInWaitingPeriod(address account, bytes32 currencyKey) public view returns (uint) {
        return secsLeftInWaitingPeriodForExchange(exchangeState().getMaxTimestamp(account, currencyKey));
    }

    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 exchangeCircuitBreaker().lastExchangeRate(currencyKey);
    }

    function settlementOwing(address account, bytes32 currencyKey)
        public
        view
        returns (
            uint reclaimAmount,
            uint rebateAmount,
            uint numEntries
        )
    {
        (reclaimAmount, rebateAmount, numEntries, ) = _settlementOwing(account, currencyKey);
    }

    // Internal function to aggregate each individual rebate and reclaim entry for a synth
    function _settlementOwing(address account, bytes32 currencyKey)
        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 = exchangeState().getLengthOfEntries(account, currencyKey);

        // For each unsettled exchange
        IExchanger.ExchangeEntrySettlement[] memory settlements = new IExchanger.ExchangeEntrySettlement[](numEntries);
        for (uint i = 0; i < numEntries; i++) {
            uint reclaim;
            uint rebate;
            // fetch the entry from storage
            IExchangeState.ExchangeEntry memory exchangeEntry = _getExchangeEntry(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(exchangeEntry);

            // given these round ids, determine what effective value they should have received
            (uint destinationAmount, , ) =
                exchangeRates().effectiveValueAndRatesAtRound(
                    exchangeEntry.src,
                    exchangeEntry.amount,
                    exchangeEntry.dest,
                    srcRoundIdAtPeriodEnd,
                    destRoundIdAtPeriodEnd
                );

            // and deduct the fee from this amount using the exchangeFeeRate from storage
            uint 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 =
                exchangeCircuitBreaker().isDeviationAboveThreshold(exchangeEntry.amountReceived, amountShouldHaveReceived);
            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(
        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 hasWaitingPeriodOrSettlementOwing(address account, bytes32 currencyKey) external view returns (bool) {
        if (maxSecsLeftInWaitingPeriod(account, currencyKey) != 0) {
            return true;
        }

        (uint reclaimAmount, , , ) = _settlementOwing(account, currencyKey);

        return reclaimAmount > 0;
    }

    /* ========== 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) = exchangeCircuitBreaker().rateWithInvalid(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");
        }

        (amountReceived, fee, vSynth) = _exchange(
            exchangeForAddress,
            sourceCurrencyKey,
            sourceAmount,
            destinationCurrencyKey,
            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) = _internalSettle(from, sourceCurrencyKey, false);

        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,
        bytes32 sourceCurrencyKey,
        uint sourceAmount,
        bytes32 destinationCurrencyKey,
        address destinationAddress,
        bool virtualSynth
    )
        internal
        returns (
            uint amountReceived,
            uint fee,
            IVirtualSynth vSynth
        )
    {
        require(sourceAmount > 0, "Zero amount");

        // Using struct to resolve stack too deep error
        IExchanger.ExchangeEntry memory entry;

        entry.roundIdForSrc = exchangeRates().getCurrentRoundId(sourceCurrencyKey);
        entry.roundIdForDest = exchangeRates().getCurrentRoundId(destinationCurrencyKey);

        uint 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, IVirtualSynth(0));
        }

        (entry.destinationAmount, entry.sourceRate, entry.destinationRate) = exchangeRates().effectiveValueAndRatesAtRound(
            sourceCurrencyKey,
            sourceAmountAfterSettlement,
            destinationCurrencyKey,
            entry.roundIdForSrc,
            entry.roundIdForDest
        );

        _ensureCanExchangeAtRound(sourceCurrencyKey, destinationCurrencyKey, entry.roundIdForSrc, entry.roundIdForDest);

        // SIP-65: Decentralized Circuit Breaker
        // mutative call to suspend system if the rate is invalid
        if (_exchangeRatesCircuitBroken(sourceCurrencyKey, destinationCurrencyKey)) {
            return (0, 0, IVirtualSynth(0));
        }

        bool tooVolatile;
        (entry.exchangeFeeRate, tooVolatile) = _feeRateForExchangeAtRounds(
            sourceCurrencyKey,
            destinationCurrencyKey,
            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 = _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(
            sourceCurrencyKey,
            from,
            sourceAmountAfterSettlement,
            destinationCurrencyKey,
            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 = 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.

        // 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(
            [sourceCurrencyKey, destinationCurrencyKey],
            [entry.sourceRate, entry.destinationRate]
        );

        // Let the DApps know there was a Synth exchange
        ISynthetixInternal(address(synthetix())).emitSynthExchange(
            from,
            sourceCurrencyKey,
            sourceAmountAfterSettlement,
            destinationCurrencyKey,
            amountReceived,
            destinationAddress
        );

        // iff the waiting period is gt 0
        if (getWaitingPeriodSecs() > 0) {
            // persist the exchange information for the dest key
            appendExchange(
                destinationAddress,
                sourceCurrencyKey,
                sourceAmountAfterSettlement,
                destinationCurrencyKey,
                amountReceived,
                entry.exchangeFeeRate
            );
        }
    }

    // SIP-65: Decentralized Circuit Breaker
    function _exchangeRatesCircuitBroken(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey)
        internal
        returns (bool circuitBroken)
    {
        // check both currencies unless they're sUSD, since its rate is never invalid (gas savings)
        if (sourceCurrencyKey != sUSD) {
            (, circuitBroken) = exchangeCircuitBreaker().rateWithBreakCircuit(sourceCurrencyKey);
        }

        if (destinationCurrencyKey != sUSD) {
            // we're not skipping the suspension check if the circuit was broken already
            // this is not terribly important, but is more consistent (so that results don't
            // depend on which synth is source and which is destination)
            bool destCircuitBroken;
            (, destCircuitBroken) = exchangeCircuitBreaker().rateWithBreakCircuit(destinationCurrencyKey);
            circuitBroken = circuitBroken || destCircuitBroken;
        }
    }

    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 _internalSettle(from, currencyKey, true);
    }

    function suspendSynthWithInvalidRate(bytes32 currencyKey) external {
        systemStatus().requireSystemActive();
        // SIP-65: Decentralized Circuit Breaker
        (, bool circuitBroken) = exchangeCircuitBreaker().rateWithBreakCircuit(currencyKey);
        require(circuitBroken, "Synth price is valid");
    }

    /* ========== INTERNAL FUNCTIONS ========== */

    function _ensureCanExchange(
        bytes32 sourceCurrencyKey,
        uint sourceAmount,
        bytes32 destinationCurrencyKey
    ) internal view {
        require(sourceCurrencyKey != destinationCurrencyKey, "Can't be same synth");
        require(sourceAmount > 0, "Zero amount");

        bytes32[] memory synthKeys = new bytes32[](2);
        synthKeys[0] = sourceCurrencyKey;
        synthKeys[1] = destinationCurrencyKey;
        require(!exchangeRates().anyRateIsInvalid(synthKeys), "src/dest rate stale or flagged");
    }

    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");
    }

    function _internalSettle(
        address from,
        bytes32 currencyKey,
        bool updateCache
    )
        internal
        returns (
            uint reclaimed,
            uint refunded,
            uint numEntriesSettled
        )
    {
        require(maxSecsLeftInWaitingPeriod(from, currencyKey) == 0, "Cannot settle during waiting period");

        (uint reclaimAmount, uint rebateAmount, uint entries, IExchanger.ExchangeEntrySettlement[] memory settlements) =
            _settlementOwing(from, currencyKey);

        if (reclaimAmount > rebateAmount) {
            reclaimed = reclaimAmount.sub(rebateAmount);
            reclaim(from, currencyKey, reclaimed);
        } else if (rebateAmount > reclaimAmount) {
            refunded = rebateAmount.sub(reclaimAmount);
            refund(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;
            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
        exchangeState().removeEntries(from, currencyKey);
    }

    function reclaim(
        address from,
        bytes32 currencyKey,
        uint amount
    ) internal {
        // burn amount from user
        issuer().synths(currencyKey).burn(from, amount);
        ISynthetixInternal(address(synthetix())).emitExchangeReclaim(from, currencyKey, amount);
    }

    function refund(
        address from,
        bytes32 currencyKey,
        uint amount
    ) internal {
        // issue amount to user
        issuer().synths(currencyKey).issue(from, amount);
        ISynthetixInternal(address(synthetix())).emitExchangeRebate(from, currencyKey, amount);
    }

    function secsLeftInWaitingPeriodForExchange(uint timestamp) internal view returns (uint) {
        uint _waitingPeriodSecs = getWaitingPeriodSecs();
        if (timestamp == 0 || now >= timestamp.add(_waitingPeriodSecs)) {
            return 0;
        }

        return timestamp.add(_waitingPeriodSecs).sub(now);
    }

    /* ========== 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) {
        (uint feeRate, bool tooVolatile) = _feeRateForExchange(sourceCurrencyKey, destinationCurrencyKey);
        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)
    {
        return _dynamicFeeRateForExchange(sourceCurrencyKey, destinationCurrencyKey);
    }

    /// @notice Calculate the exchange fee for a given source and destination currency key
    /// @param sourceCurrencyKey The source currency key
    /// @param destinationCurrencyKey The destination currency key
    /// @return The exchange fee rate
    /// @return The exchange dynamic fee rate and if rates are too volatile
    function _feeRateForExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey)
        internal
        view
        returns (uint feeRate, bool tooVolatile)
    {
        // Get the exchange fee rate as per the source currencyKey and destination currencyKey
        uint baseRate = getExchangeFeeRate(sourceCurrencyKey).add(getExchangeFeeRate(destinationCurrencyKey));
        uint dynamicFee;
        (dynamicFee, tooVolatile) = _dynamicFeeRateForExchange(sourceCurrencyKey, destinationCurrencyKey);
        return (baseRate.add(dynamicFee), tooVolatile);
    }

    /// @notice Calculate the exchange fee for a given source and destination currency key
    /// @param sourceCurrencyKey The source currency key
    /// @param destinationCurrencyKey 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(
        bytes32 sourceCurrencyKey,
        bytes32 destinationCurrencyKey,
        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 = getExchangeFeeRate(sourceCurrencyKey).add(getExchangeFeeRate(destinationCurrencyKey));
        uint dynamicFee;
        (dynamicFee, tooVolatile) = _dynamicFeeRateForExchangeAtRounds(
            sourceCurrencyKey,
            destinationCurrencyKey,
            roundIdForSrc,
            roundIdForDest
        );
        return (baseRate.add(dynamicFee), tooVolatile);
    }

    function _dynamicFeeRateForExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey)
        internal
        view
        returns (uint dynamicFee, bool tooVolatile)
    {
        DynamicFeeConfig memory config = getExchangeDynamicFeeConfig();
        (uint dynamicFeeDst, bool dstVolatile) = _dynamicFeeRateForCurrency(destinationCurrencyKey, config);
        (uint dynamicFeeSrc, bool srcVolatile) = _dynamicFeeRateForCurrency(sourceCurrencyKey, config);
        dynamicFee = dynamicFeeDst.add(dynamicFeeSrc);
        // cap to maxFee
        bool overMax = dynamicFee > config.maxFee;
        dynamicFee = overMax ? config.maxFee : dynamicFee;
        return (dynamicFee, overMax || dstVolatile || srcVolatile);
    }

    function _dynamicFeeRateForExchangeAtRounds(
        bytes32 sourceCurrencyKey,
        bytes32 destinationCurrencyKey,
        uint roundIdForSrc,
        uint roundIdForDest
    ) internal view returns (uint dynamicFee, bool tooVolatile) {
        DynamicFeeConfig memory config = getExchangeDynamicFeeConfig();
        (uint dynamicFeeDst, bool dstVolatile) =
            _dynamicFeeRateForCurrencyRound(destinationCurrencyKey, roundIdForDest, config);
        (uint dynamicFeeSrc, bool srcVolatile) = _dynamicFeeRateForCurrencyRound(sourceCurrencyKey, roundIdForSrc, config);
        dynamicFee = dynamicFeeDst.add(dynamicFeeSrc);
        // cap to maxFee
        bool overMax = dynamicFee > config.maxFee;
        dynamicFee = overMax ? config.maxFee : dynamicFee;
        return (dynamicFee, overMax || dstVolatile || srcVolatile);
    }

    /// @notice Get dynamic dynamicFee for a given currency key (SIP-184)
    /// @param currencyKey The given currency key
    /// @param config dynamic fee calculation configuration params
    /// @return The dynamic fee and if it exceeds max dynamic fee set in config
    function _dynamicFeeRateForCurrency(bytes32 currencyKey, DynamicFeeConfig memory config)
        internal
        view
        returns (uint dynamicFee, bool tooVolatile)
    {
        // no dynamic dynamicFee for sUSD or too few rounds
        if (currencyKey == sUSD || config.rounds <= 1) {
            return (0, false);
        }
        uint roundId = exchangeRates().getCurrentRoundId(currencyKey);
        return _dynamicFeeRateForCurrencyRound(currencyKey, roundId, config);
    }

    /// @notice Get dynamicFee for a given currency key (SIP-184)
    /// @param currencyKey The given currency key
    /// @param roundId The round id
    /// @param config dynamic fee calculation configuration params
    /// @return The dynamic fee and if it exceeds max dynamic fee set in config
    function _dynamicFeeRateForCurrencyRound(
        bytes32 currencyKey,
        uint roundId,
        DynamicFeeConfig memory config
    ) internal view returns (uint dynamicFee, bool tooVolatile) {
        // no dynamic dynamicFee for sUSD or too few rounds
        if (currencyKey == sUSD || config.rounds <= 1) {
            return (0, false);
        }
        uint[] memory prices;
        (prices, ) = exchangeRates().ratesAndUpdatedTimeForCurrencyLastNRounds(currencyKey, config.rounds, roundId);
        dynamicFee = _dynamicFeeCalculation(prices, config.threshold, config.weightDecay);
        // cap to maxFee
        bool overMax = dynamicFee > config.maxFee;
        dynamicFee = overMax ? config.maxFee : 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
        )
    {
        // 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);

        // check rates don't deviate above ciruit breaker allowed deviation
        (, bool srcInvalid) = exchangeCircuitBreaker().rateWithInvalid(sourceCurrencyKey);
        (, bool dstInvalid) = exchangeCircuitBreaker().rateWithInvalid(destinationCurrencyKey);
        require(!srcInvalid, "source synth rate invalid");
        require(!dstInvalid, "destination synth rate invalid");

        // check rates not stale or flagged
        _ensureCanExchange(sourceCurrencyKey, sourceAmount, destinationCurrencyKey);

        bool tooVolatile;
        (exchangeFeeRate, tooVolatile) = _feeRateForExchange(sourceCurrencyKey, destinationCurrencyKey);

        // check rates volatility result
        require(!tooVolatile, "exchange rates too volatile");

        (uint destinationAmount, , ) =
            exchangeRates().effectiveValueAndRates(sourceCurrencyKey, sourceAmount, destinationCurrencyKey);

        amountReceived = _deductFeesFromAmount(destinationAmount, exchangeFeeRate);
        fee = destinationAmount.sub(amountReceived);
    }

    function _deductFeesFromAmount(uint destinationAmount, uint exchangeFeeRate)
        internal
        pure
        returns (uint amountReceived)
    {
        amountReceived = destinationAmount.multiplyDecimal(SafeDecimalMath.unit().sub(exchangeFeeRate));
    }

    function appendExchange(
        address account,
        bytes32 src,
        uint amount,
        bytes32 dest,
        uint amountReceived,
        uint exchangeFeeRate
    ) internal {
        IExchangeRates exRates = exchangeRates();
        uint roundIdForSrc = exRates.getCurrentRoundId(src);
        uint roundIdForDest = exRates.getCurrentRoundId(dest);
        exchangeState().appendExchangeEntry(
            account,
            src,
            amount,
            dest,
            amountReceived,
            exchangeFeeRate,
            now,
            roundIdForSrc,
            roundIdForDest
        );

        emit ExchangeEntryAppended(
            account,
            src,
            amount,
            dest,
            amountReceived,
            exchangeFeeRate,
            roundIdForSrc,
            roundIdForDest
        );
    }

    function getRoundIdsAtPeriodEnd(IExchangeState.ExchangeEntry memory exchangeEntry)
        internal
        view
        returns (uint srcRoundIdAtPeriodEnd, uint destRoundIdAtPeriodEnd)
    {
        IExchangeRates exRates = exchangeRates();
        uint _waitingPeriodSecs = getWaitingPeriodSecs();

        srcRoundIdAtPeriodEnd = exRates.getLastRoundIdBeforeElapsedSecs(
            exchangeEntry.src,
            exchangeEntry.roundIdForSrc,
            exchangeEntry.timestamp,
            _waitingPeriodSecs
        );
        destRoundIdAtPeriodEnd = exRates.getLastRoundIdBeforeElapsedSecs(
            exchangeEntry.dest,
            exchangeEntry.roundIdForDest,
            exchangeEntry.timestamp,
            _waitingPeriodSecs
        );
    }

    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 ==========
    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
    );
}


// https://docs.synthetix.io/contracts/source/contracts/minimalproxyfactory
contract MinimalProxyFactory {
    function _cloneAsMinimalProxy(address _base, string memory _revertMsg) internal returns (address clone) {
        bytes memory createData = _generateMinimalProxyCreateData(_base);

        assembly {
            clone := create(
                0, // no value
                add(createData, 0x20), // data
                55 // data is always 55 bytes (10 constructor + 45 code)
            )
        }

        // If CREATE fails for some reason, address(0) is returned
        require(clone != address(0), _revertMsg);
    }

    function _generateMinimalProxyCreateData(address _base) internal pure returns (bytes memory) {
        return
            abi.encodePacked(
                //---- constructor -----
                bytes10(0x3d602d80600a3d3981f3),
                //---- proxy code -----
                bytes10(0x363d3d373d3d3d363d73),
                _base,
                bytes15(0x5af43d82803e903d91602b57fd5bf3)
            );
    }
}


// Inheritance


// Internal references


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)
    {
        exchangeFeeRate = _feeRateForAtomicExchange(sourceCurrencyKey, destinationCurrencyKey);
    }

    function getAmountsForAtomicExchange(
        uint sourceAmount,
        bytes32 sourceCurrencyKey,
        bytes32 destinationCurrencyKey
    )
        external
        view
        returns (
            uint amountReceived,
            uint fee,
            uint exchangeFeeRate
        )
    {
        (amountReceived, fee, exchangeFeeRate, , , ) = _getAmountsForAtomicExchangeMinusFees(
            sourceAmount,
            sourceCurrencyKey,
            destinationCurrencyKey
        );
    }

    /* ========== 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) {
        _ensureCanExchange(sourceCurrencyKey, sourceAmount, destinationCurrencyKey);
        require(!exchangeRates().synthTooVolatileForAtomicExchange(sourceCurrencyKey), "Src synth too volatile");
        require(!exchangeRates().synthTooVolatileForAtomicExchange(destinationCurrencyKey), "Dest synth too volatile");

        uint 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);
        }

        uint exchangeFeeRate;
        uint systemConvertedAmount;
        uint systemSourceRate;
        uint systemDestinationRate;

        // 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, sourceCurrencyKey, destinationCurrencyKey);

        // SIP-65: Decentralized Circuit Breaker (checking current system rates)
        if (_exchangeRatesCircuitBroken(sourceCurrencyKey, destinationCurrencyKey)) {
            return (0, 0);
        }

        // Sanity check atomic output's value against current system value (checking atomic rates)
        require(
            !exchangeCircuitBreaker().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, sourceCurrencyKey, sUSD);
            sourceSusdValue = amountReceivedInUSD.add(sUsdFee);
        }

        // Check and update atomic volume limit
        _checkAndUpdateAtomicVolume(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(uint sourceSusdValue) internal {
        uint currentVolume =
            uint(lastAtomicVolume.time) == block.timestamp
                ? uint(lastAtomicVolume.volume).add(sourceSusdValue)
                : sourceSusdValue;
        require(currentVolume <= getAtomicMaxVolumePerBlock(), "Surpassed volume limit");
        lastAtomicVolume.time = uint64(block.timestamp);
        lastAtomicVolume.volume = uint192(currentVolume); // Protected by volume limit check above
    }

    function _feeRateForAtomicExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey)
        internal
        view
        returns (uint)
    {
        // Get the exchange fee rate as per source and destination currencyKey
        uint baseRate = getAtomicExchangeFeeRate(sourceCurrencyKey).add(getAtomicExchangeFeeRate(destinationCurrencyKey));
        if (baseRate == 0) {
            // If no atomic rate was set, fallback to the regular exchange rate
            baseRate = getExchangeFeeRate(sourceCurrencyKey).add(getExchangeFeeRate(destinationCurrencyKey));
        }

        return baseRate;
    }

    function _getAmountsForAtomicExchangeMinusFees(
        uint sourceAmount,
        bytes32 sourceCurrencyKey,
        bytes32 destinationCurrencyKey
    )
        internal
        view
        returns (
            uint amountReceived,
            uint fee,
            uint exchangeFeeRate,
            uint systemConvertedAmount,
            uint systemSourceRate,
            uint systemDestinationRate
        )
    {
        uint destinationAmount;
        (destinationAmount, systemConvertedAmount, systemSourceRate, systemDestinationRate) = exchangeRates()
            .effectiveAtomicValueAndRates(sourceCurrencyKey, sourceAmount, destinationCurrencyKey);

        exchangeFeeRate = _feeRateForAtomicExchange(sourceCurrencyKey, destinationCurrencyKey);
        amountReceived = _deductFeesFromAmount(destinationAmount, exchangeFeeRate);
        fee = destinationAmount.sub(amountReceived);
    }

    event VirtualSynthCreated(
        address indexed synth,
        address indexed recipient,
        address vSynth,
        bytes32 currencyKey,
        uint amount
    );
}

Contract ABI

[{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"address","name":"_resolver","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"name","type":"bytes32"},{"indexed":false,"internalType":"address","name":"destination","type":"address"}],"name":"CacheUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"bytes32","name":"src","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"dest","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"amountReceived","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"exchangeFeeRate","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"roundIdForSrc","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"roundIdForDest","type":"uint256"}],"name":"ExchangeEntryAppended","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"bytes32","name":"src","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"dest","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"reclaim","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"rebate","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"srcRoundIdAtPeriodEnd","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"destRoundIdAtPeriodEnd","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"exchangeTimestamp","type":"uint256"}],"name":"ExchangeEntrySettled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldOwner","type":"address"},{"indexed":false,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnerNominated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"synth","type":"address"},{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"address","name":"vSynth","type":"address"},{"indexed":false,"internalType":"bytes32","name":"currencyKey","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"VirtualSynthCreated","type":"event"},{"constant":true,"inputs":[],"name":"CONTRACT_NAME","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"acceptOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"atomicMaxVolumePerBlock","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"bytes32","name":"currencyKey","type":"bytes32"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"refunded","type":"uint256"}],"name":"calculateAmountAfterSettlement","outputs":[{"internalType":"uint256","name":"amountAfterSettlement","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"sourceCurrencyKey","type":"bytes32"},{"internalType":"bytes32","name":"destinationCurrencyKey","type":"bytes32"}],"name":"dynamicFeeRateForExchange","outputs":[{"internalType":"uint256","name":"feeRate","type":"uint256"},{"internalType":"bool","name":"tooVolatile","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"exchangeForAddress","type":"address"},{"internalType":"address","name":"from","type":"address"},{"internalType":"bytes32","name":"sourceCurrencyKey","type":"bytes32"},{"internalType":"uint256","name":"sourceAmount","type":"uint256"},{"internalType":"bytes32","name":"destinationCurrencyKey","type":"bytes32"},{"internalType":"address","name":"destinationAddress","type":"address"},{"internalType":"bool","name":"virtualSynth","type":"bool"},{"internalType":"address","name":"rewardAddress","type":"address"},{"internalType":"bytes32","name":"trackingCode","type":"bytes32"}],"name":"exchange","outputs":[{"internalType":"uint256","name":"amountReceived","type":"uint256"},{"internalType":"contract IVirtualSynth","name":"vSynth","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"bytes32","name":"sourceCurrencyKey","type":"bytes32"},{"internalType":"uint256","name":"sourceAmount","type":"uint256"},{"internalType":"bytes32","name":"destinationCurrencyKey","type":"bytes32"},{"internalType":"address","name":"destinationAddress","type":"address"},{"internalType":"bytes32","name":"trackingCode","type":"bytes32"},{"internalType":"uint256","name":"minAmount","type":"uint256"}],"name":"exchangeAtomically","outputs":[{"internalType":"uint256","name":"amountReceived","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"sourceCurrencyKey","type":"bytes32"},{"internalType":"bytes32","name":"destinationCurrencyKey","type":"bytes32"}],"name":"feeRateForAtomicExchange","outputs":[{"internalType":"uint256","name":"exchangeFeeRate","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"sourceCurrencyKey","type":"bytes32"},{"internalType":"bytes32","name":"destinationCurrencyKey","type":"bytes32"}],"name":"feeRateForExchange","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"sourceAmount","type":"uint256"},{"internalType":"bytes32","name":"sourceCurrencyKey","type":"bytes32"},{"internalType":"bytes32","name":"destinationCurrencyKey","type":"bytes32"}],"name":"getAmountsForAtomicExchange","outputs":[{"internalType":"uint256","name":"amountReceived","type":"uint256"},{"internalType":"uint256","name":"fee","type":"uint256"},{"internalType":"uint256","name":"exchangeFeeRate","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"sourceAmount","type":"uint256"},{"internalType":"bytes32","name":"sourceCurrencyKey","type":"bytes32"},{"internalType":"bytes32","name":"destinationCurrencyKey","type":"bytes32"}],"name":"getAmountsForExchange","outputs":[{"internalType":"uint256","name":"amountReceived","type":"uint256"},{"internalType":"uint256","name":"fee","type":"uint256"},{"internalType":"uint256","name":"exchangeFeeRate","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"bytes32","name":"currencyKey","type":"bytes32"}],"name":"hasWaitingPeriodOrSettlementOwing","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isResolverCached","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"currencyKey","type":"bytes32"}],"name":"isSynthRateInvalid","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"lastAtomicVolume","outputs":[{"internalType":"uint64","name":"time","type":"uint64"},{"internalType":"uint192","name":"volume","type":"uint192"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"currencyKey","type":"bytes32"}],"name":"lastExchangeRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"bytes32","name":"currencyKey","type":"bytes32"}],"name":"maxSecsLeftInWaitingPeriod","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"nominateNewOwner","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"nominatedOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"priceDeviationThresholdFactor","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"rebuildCache","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"resolver","outputs":[{"internalType":"contract AddressResolver","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"resolverAddressesRequired","outputs":[{"internalType":"bytes32[]","name":"addresses","type":"bytes32[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"bytes32","name":"currencyKey","type":"bytes32"}],"name":"settle","outputs":[{"internalType":"uint256","name":"reclaimed","type":"uint256"},{"internalType":"uint256","name":"refunded","type":"uint256"},{"internalType":"uint256","name":"numEntriesSettled","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"bytes32","name":"currencyKey","type":"bytes32"}],"name":"settlementOwing","outputs":[{"internalType":"uint256","name":"reclaimAmount","type":"uint256"},{"internalType":"uint256","name":"rebateAmount","type":"uint256"},{"internalType":"uint256","name":"numEntries","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"currencyKey","type":"bytes32"}],"name":"suspendSynthWithInvalidRate","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"tradingRewardsEnabled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"waitingPeriodSecs","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}]

60806040523480156200001157600080fd5b5060405162005b0838038062005b08833981810160405260408110156200003757600080fd5b50805160209091015181818080836001600160a01b038116620000a1576040805162461bcd60e51b815260206004820152601960248201527f4f776e657220616464726573732063616e6e6f74206265203000000000000000604482015290519081900360640190fd5b600080546001600160a01b0319166001600160a01b038316908117825560408051928352602083019190915280517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c9281900390910190a150600280546001600160a01b039092166001600160a01b031990921691909117905550505050506159d880620001306000396000f3fe608060405234801561001057600080fd5b50600436106101cf5760003560e01c806357af302c116101045780638da5cb5b116100a2578063c39def0b11610071578063c39def0b14610568578063d6f32e06146105a4578063dc703e73146105d0578063f450aa34146105f9576101cf565b80638da5cb5b1461051c5780638e52049c14610524578063a4bca13114610558578063c193f0d814610560576101cf565b806379ba5097116100de57806379ba5097146104975780637dd1a57a1461049f57806389257117146104bc578063899ffef4146104c4576101cf565b806357af302c1461046a578063614d08f814610487578063741853601461048f576101cf565b80631b16802c11610171578063372a395a1161014b578063372a395a1461039f5780634c268fc8146103a75780634f8633d2146103df57806353a47bb714610462576101cf565b80631b16802c1461030b5780632af64bd31461033757806333a7688014610353576101cf565b80630b9e31c9116101ad5780630b9e31c9146102595780631627540c1461027857806319d5c6651461029e5780631a5c6095146102e8576101cf565b806304f3bcec146101d4578063059c29ec146101f85780630ae81a5e14610236575b600080fd5b6101dc610622565b604080516001600160a01b039092168252519081900360200190f35b6102246004803603604081101561020e57600080fd5b506001600160a01b038135169060200135610631565b60408051918252519081900360200190f35b6102246004803603604081101561024c57600080fd5b50803590602001356106d5565b6102766004803603602081101561026f57600080fd5b50356106e1565b005b6102766004803603602081101561028e57600080fd5b50356001600160a01b0316610805565b6102ca600480360360408110156102b457600080fd5b506001600160a01b038135169060200135610861565b60408051938452602084019290925282820152519081900360600190f35b610224600480360360408110156102fe57600080fd5b508035906020013561087f565b6102ca6004803603604081101561032157600080fd5b506001600160a01b0381351690602001356108dc565b61033f61095d565b604080519115158252519081900360200190f35b610224600480360360e081101561036957600080fd5b506001600160a01b0381358116916020810135916040820135916060810135916080820135169060a08101359060c00135610a68565b610224610bb7565b610224600480360360808110156103bd57600080fd5b506001600160a01b038135169060208101359060408101359060600135610bc6565b61044160048036036101208110156103f657600080fd5b506001600160a01b038135811691602081013582169160408201359160608101359160808201359160a081013582169160c082013515159160e0810135909116906101000135610ce6565b604080519283526001600160a01b0390911660208301528051918290030190f35b6101dc610f0c565b61033f6004803603602081101561048057600080fd5b5035610f1b565b610224610fa0565b610276610fc4565b610276611188565b610224600480360360208110156104b557600080fd5b5035611244565b6102246112c3565b6104cc6112cd565b60408051602080825283518183015283519192839290830191858101910280838360005b838110156105085781810151838201526020016104f0565b505050509050019250505060405180910390f35b6101dc611342565b61052c611351565b6040805167ffffffffffffffff90931683526001600160c01b0390911660208301528051918290030190f35b610224611373565b61033f61137d565b61058b6004803603604081101561057e57600080fd5b5080359060200135611387565b6040805192835290151560208301528051918290030190f35b61033f600480360360408110156105ba57600080fd5b506001600160a01b0381351690602001356113a0565b6102ca600480360360608110156105e657600080fd5b50803590602081013590604001356113d2565b6102ca6004803603606081101561060f57600080fd5b50803590602081013590604001356113f5565b6002546001600160a01b031681565b60006106cc61063e611785565b6001600160a01b031663f1406dc885856040518363ffffffff1660e01b815260040180836001600160a01b03166001600160a01b031681526020018281526020019250505060206040518083038186803b15801561069b57600080fd5b505afa1580156106af573d6000803e3d6000fd5b505050506040513d60208110156106c557600080fd5b50516117a0565b90505b92915050565b60006106cc83836117ff565b6106e9611847565b6001600160a01b031663086dabd16040518163ffffffff1660e01b815260040160006040518083038186803b15801561072157600080fd5b505afa158015610735573d6000803e3d6000fd5b505050506000610743611861565b6001600160a01b031663a47af19e836040518263ffffffff1660e01b8152600401808281526020019150506040805180830381600087803b15801561078757600080fd5b505af115801561079b573d6000803e3d6000fd5b505050506040513d60408110156107b157600080fd5b5060200151905080610801576040805162461bcd60e51b815260206004820152601460248201527314de5b9d1a081c1c9a58d9481a5cc81d985b1a5960621b604482015290519081900360640190fd5b5050565b61080d611885565b600180546001600160a01b0383166001600160a01b0319909116811790915560408051918252517f906a1c6bd7e3091ea86693dd029a831c19049ce77f1dce2ce0bab1cacbabce229181900360200190a150565b600080600061087085856118d0565b50919790965090945092505050565b600080600061088e8585611c19565b9150915080156108d4576040805162461bcd60e51b815260206004820152600c60248201526b746f6f20766f6c6174696c6560a01b604482015290519081900360640190fd5b509392505050565b60008060006108e9611847565b6001600160a01b03166342a28e21856040518263ffffffff1660e01b81526004018082815260200191505060006040518083038186803b15801561092c57600080fd5b505afa158015610940573d6000803e3d6000fd5b5050505061095085856001611c60565b9250925092509250925092565b600060606109696112cd565b905060005b8151811015610a5e57600082828151811061098557fe5b6020908102919091018101516000818152600383526040908190205460025482516321f8a72160e01b81526004810185905292519395506001600160a01b03918216949116926321f8a721926024808201939291829003018186803b1580156109ed57600080fd5b505afa158015610a01573d6000803e3d6000fd5b505050506040513d6020811015610a1757600080fd5b50516001600160a01b0316141580610a4457506000818152600360205260409020546001600160a01b0316155b15610a555760009350505050610a65565b5060010161096e565b5060019150505b90565b600080610a73611fe5565b9050336001600160a01b0382161480610b005750604080516316b2213f60e01b815233600482015290516000916001600160a01b038416916316b2213f91602480820192602092909190829003018186803b158015610ad157600080fd5b505afa158015610ae5573d6000803e3d6000fd5b505050506040513d6020811015610afb57600080fd5b505114155b610b3b5760405162461bcd60e51b81526004018080602001828103825260458152602001806158ec6045913960600191505060405180910390fd5b6000610b4a8a8a8a8a8a611ffc565b909350905083831015610b8e5760405162461bcd60e51b815260040180806020018281038252603a8152602001806158b2603a913960400191505060405180910390fd5b610b988187612752565b8415610baa57610baa85888584612801565b5050979650505050505050565b6000610bc1612884565b905090565b816000610bd161293b565b6001600160a01b03166332608039866040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b158015610c1457600080fd5b505afa158015610c28573d6000803e3d6000fd5b505050506040513d6020811015610c3e57600080fd5b5051604080516370a0823160e01b81526001600160a01b038981166004830152915191909216916370a08231916024808301926020929190829003018186803b158015610c8a57600080fd5b505afa158015610c9e573d6000803e3d6000fd5b505050506040513d6020811015610cb457600080fd5b5051905080821115610cc4578091505b8215610cdd57610cda828463ffffffff61294f16565b91505b50949350505050565b6000806000610cf3611fe5565b9050336001600160a01b0382161480610d805750604080516316b2213f60e01b815233600482015290516000916001600160a01b038416916316b2213f91602480820192602092909190829003018186803b158015610d5157600080fd5b505afa158015610d65573d6000803e3d6000fd5b505050506040513d6020811015610d7b57600080fd5b505114155b610dbb5760405162461bcd60e51b81526004018080602001828103825260458152602001806158ec6045913960600191505060405180910390fd5b60008c6001600160a01b03168c6001600160a01b031614610eca57610dde6129a9565b6001600160a01b031663faf431bb8e8e6040518363ffffffff1660e01b815260040180836001600160a01b03166001600160a01b03168152602001826001600160a01b03166001600160a01b031681526020019250505060206040518083038186803b158015610e4d57600080fd5b505afa158015610e61573d6000803e3d6000fd5b505050506040513d6020811015610e7757600080fd5b5051610eca576040805162461bcd60e51b815260206004820152601d60248201527f4e6f7420617070726f76656420746f20616374206f6e20626568616c66000000604482015290519081900360640190fd5b610ed88d8c8c8c8c8c6129c8565b9195509093509050610eea8187612752565b8415610efc57610efc858a8684612801565b5050995099975050505050505050565b6001546001600160a01b031681565b600080610f26611861565b6001600160a01b031663cb1ec317846040518263ffffffff1660e01b815260040180828152602001915050604080518083038186803b158015610f6857600080fd5b505afa158015610f7c573d6000803e3d6000fd5b505050506040513d6040811015610f9257600080fd5b50602001519150505b919050565b7f45786368616e67657257697468466565526563416c7465726e6174697665730081565b6060610fce6112cd565b905060005b8151811015610801576000828281518110610fea57fe5b602090810291909101810151600254604080517f5265736f6c766572206d697373696e67207461726765743a2000000000000000818601526039808201859052825180830390910181526059820180845263dacb2d0160e01b9052605d8201858152607d83019384528151609d84015281519597506000966001600160a01b039095169563dacb2d01958995939492939260bd0191908501908083838c5b838110156110a0578181015183820152602001611088565b50505050905090810190601f1680156110cd5780820380516001836020036101000a031916815260200191505b50935050505060206040518083038186803b1580156110eb57600080fd5b505afa1580156110ff573d6000803e3d6000fd5b505050506040513d602081101561111557600080fd5b505160008381526003602090815260409182902080546001600160a01b0319166001600160a01b03851690811790915582518681529182015281519293507f88a93678a3692f6789d9546fc621bf7234b101ddb7d4fe479455112831b8aa68929081900390910190a15050600101610fd3565b6001546001600160a01b031633146111d15760405162461bcd60e51b815260040180806020018281038252603581526020018061587d6035913960400191505060405180910390fd5b600054600154604080516001600160a01b03938416815292909116602083015280517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c9281900390910190a160018054600080546001600160a01b03199081166001600160a01b03841617909155169055565b600061124e611861565b6001600160a01b0316637dd1a57a836040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b15801561129157600080fd5b505afa1580156112a5573d6000803e3d6000fd5b505050506040513d60208110156112bb57600080fd5b505192915050565b6000610bc1612ffc565b6060806112d8613076565b6040805160018082528183019092529192506060919060208083019080388339019050509050755669727475616c53796e74684d6173746572636f707960501b8160008151811061132557fe5b60200260200101818152505061133b828261323a565b9250505090565b6000546001600160a01b031681565b60045467ffffffffffffffff811690600160401b90046001600160c01b031682565b6000610bc16132f6565b6000610bc161337c565b60008061139484846133fa565b915091505b9250929050565b60006113ac8383610631565b156113b9575060016106cf565b60006113c584846118d0565b5050501515949350505050565b60008060006113e2868686613482565b50939a9299509097509095505050505050565b6000806000611402611847565b6001600160a01b03166342a28e21866040518263ffffffff1660e01b81526004018082815260200191505060006040518083038186803b15801561144557600080fd5b505afa158015611459573d6000803e3d6000fd5b50505050611465611847565b6001600160a01b03166342a28e21856040518263ffffffff1660e01b81526004018082815260200191505060006040518083038186803b1580156114a857600080fd5b505afa1580156114bc573d6000803e3d6000fd5b5050505060006114ca611861565b6001600160a01b031663cb1ec317876040518263ffffffff1660e01b815260040180828152602001915050604080518083038186803b15801561150c57600080fd5b505afa158015611520573d6000803e3d6000fd5b505050506040513d604081101561153657600080fd5b506020015190506000611547611861565b6001600160a01b031663cb1ec317876040518263ffffffff1660e01b815260040180828152602001915050604080518083038186803b15801561158957600080fd5b505afa15801561159d573d6000803e3d6000fd5b505050506040513d60408110156115b357600080fd5b50602001519050811561160d576040805162461bcd60e51b815260206004820152601960248201527f736f757263652073796e7468207261746520696e76616c696400000000000000604482015290519081900360640190fd5b8015611660576040805162461bcd60e51b815260206004820152601e60248201527f64657374696e6174696f6e2073796e7468207261746520696e76616c69640000604482015290519081900360640190fd5b61166b878988613567565b60006116778888611c19565b909450905080156116cf576040805162461bcd60e51b815260206004820152601b60248201527f65786368616e676520726174657320746f6f20766f6c6174696c650000000000604482015290519081900360640190fd5b60006116d9613756565b6001600160a01b0316638295016a8a8c8b6040518463ffffffff1660e01b815260040180848152602001838152602001828152602001935050505060606040518083038186803b15801561172c57600080fd5b505afa158015611740573d6000803e3d6000fd5b505050506040513d606081101561175657600080fd5b505190506117648186613771565b9650611776818863ffffffff61380316565b95505050505093509350939050565b6000610bc16c45786368616e6765537461746560981b613860565b6000806117ab612ffc565b90508215806117c957506117c5838263ffffffff61294f16565b4210155b156117d8576000915050610f9b565b6117f8426117ec858463ffffffff61294f16565b9063ffffffff61380316565b9392505050565b60008061182361180e8461393d565b6118178661393d565b9063ffffffff61294f16565b9050806106cc5761183f611836846139e9565b611817866139e9565b949350505050565b6000610bc16b53797374656d53746174757360a01b613860565b6000610bc17522bc31b430b733b2a1b4b931bab4ba213932b0b5b2b960511b613860565b6000546001600160a01b031633146118ce5760405162461bcd60e51b815260040180806020018281038252602f815260200180615931602f913960400191505060405180910390fd5b565b600080600060606118df611785565b6001600160a01b031663b44e975387876040518363ffffffff1660e01b815260040180836001600160a01b03166001600160a01b031681526020018281526020019250505060206040518083038186803b15801561193c57600080fd5b505afa158015611950573d6000803e3d6000fd5b505050506040513d602081101561196657600080fd5b50516040805182815260208084028201019091529092506060908380156119a757816020015b6119946157cc565b81526020019060019003908161198c5790505b50905060005b83811015611c0d576000806119c06157cc565b6119cb8b8b86613a8f565b90506000806119d983613baa565b9150915060006119e7613756565b6001600160a01b0316636ce66c8085600001518660200151876040015187876040518663ffffffff1660e01b8152600401808681526020018581526020018481526020018381526020018281526020019550505050505060606040518083038186803b158015611a5657600080fd5b505afa158015611a6a573d6000803e3d6000fd5b505050506040513d6060811015611a8057600080fd5b50516080850151909150600090611a98908390613771565b90506000611aa4611861565b6001600160a01b03166378cb51cb8760600151846040518363ffffffff1660e01b8152600401808381526020018281526020019250505060206040518083038186803b158015611af357600080fd5b505afa158015611b07573d6000803e3d6000fd5b505050506040513d6020811015611b1d57600080fd5b5051905080611b97578186606001511115611b60576060860151611b47908363ffffffff61380316565b9750611b598e8963ffffffff61294f16565b9d50611b97565b8560600151821115611b97576060860151611b8290839063ffffffff61380316565b9650611b948d8863ffffffff61294f16565b9c505b6040518061010001604052808760000151815260200187602001518152602001876040015181526020018981526020018881526020018681526020018581526020018760a001518152508a8a81518110611bed57fe5b6020026020010181905250505050505050505080806001019150506119ad565b50905092959194509250565b6000806000611c33611c2a856139e9565b611817876139e9565b90506000611c4186866133fa565b93509050611c55828263ffffffff61294f16565b935050509250929050565b6000806000611c6f8686610631565b15611cab5760405162461bcd60e51b81526004018080602001828103825260238152602001806159816023913960400191505060405180910390fd5b60008060006060611cbc8a8a6118d0565b935093509350935082841115611cee57611cdc848463ffffffff61380316565b9650611ce98a8a89613cfd565b611d13565b83831115611d1357611d06838563ffffffff61380316565b9550611d138a8a88613e6a565b878015611d2a57506000871180611d2a5750600086115b15611e1057604080516001808252818301909252606091602080830190803883390190505090508981600081518110611d5f57fe5b602002602001018181525050611d73613fba565b6001600160a01b031663cda218c7826040518263ffffffff1660e01b81526004018080602001828103825283818151815260200191508051906020019060200280838360005b83811015611dd1578181015183820152602001611db9565b5050505090500192505050600060405180830381600087803b158015611df657600080fd5b505af1158015611e0a573d6000803e3d6000fd5b50505050505b60005b8151811015611f55578a6001600160a01b03167f8e3ad1f68bec55de3b6fa12ae2674a2a683a17c918a4cbf5157ac5d9ddc6e940838381518110611e5357fe5b602002602001015160000151848481518110611e6b57fe5b602002602001015160200151858581518110611e8357fe5b602002602001015160400151868681518110611e9b57fe5b602002602001015160600151878781518110611eb357fe5b602002602001015160800151888881518110611ecb57fe5b602002602001015160a00151898981518110611ee357fe5b602002602001015160c001518a8a81518110611efb57fe5b602002602001015160e00151604051808981526020018881526020018781526020018681526020018581526020018481526020018381526020018281526020019850505050505050505060405180910390a2600101611e13565b50819450611f61611785565b6001600160a01b031663d0d3d62a8b8b6040518363ffffffff1660e01b815260040180836001600160a01b03166001600160a01b0316815260200182815260200192505050600060405180830381600087803b158015611fc057600080fd5b505af1158015611fd4573d6000803e3d6000fd5b505050505050505093509350939050565b6000610bc1680a6f2dce8d0cae8d2f60bb1b613860565b60008061200a868686613567565b612012613756565b6001600160a01b0316638661cc7b876040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b15801561205557600080fd5b505afa158015612069573d6000803e3d6000fd5b505050506040513d602081101561207f57600080fd5b5051156120cc576040805162461bcd60e51b81526020600482015260166024820152755372632073796e746820746f6f20766f6c6174696c6560501b604482015290519081900360640190fd5b6120d4613756565b6001600160a01b0316638661cc7b856040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b15801561211757600080fd5b505afa15801561212b573d6000803e3d6000fd5b505050506040513d602081101561214157600080fd5b505115612195576040805162461bcd60e51b815260206004820152601760248201527f446573742073796e746820746f6f20766f6c6174696c65000000000000000000604482015290519081900360640190fd5b60006121a2868989613fd1565b9050806121b6575060009150819050612748565b6000806000806121c7858c8b613482565b949b509299509096509450925090506121e08b8a614009565b156121f75750600095508594506127489350505050565b6121ff611861565b6001600160a01b03166378cb51cb8461221e8a8a63ffffffff61294f16565b6040518363ffffffff1660e01b8152600401808381526020018281526020019250505060206040518083038186803b15801561225957600080fd5b505afa15801561226d573d6000803e3d6000fd5b505050506040513d602081101561228357600080fd5b5051156122d7576040805162461bcd60e51b815260206004820152601d60248201527f41746f6d6963207261746520646576696174657320746f6f206d756368000000604482015290519081900360640190fd5b6000631cd554d160e21b8c14156122ef57508461233a565b631cd554d160e21b8a141561230557508261233a565b60008061231a888f631cd554d160e21b613482565b5050505091509150612335818361294f90919063ffffffff16565b925050505b61234381614132565b6123538c8e888d8c8e6000614200565b5086156125ad57612362613756565b6001600160a01b031663654a60ac8b89631cd554d160e21b6040518463ffffffff1660e01b815260040180848152602001838152602001828152602001935050505060206040518083038186803b1580156123bc57600080fd5b505afa1580156123d0573d6000803e3d6000fd5b505050506040513d60208110156123e657600080fd5b505196506123f261293b565b6001600160a01b03166332608039631cd554d160e21b6040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b15801561243c57600080fd5b505afa158015612450573d6000803e3d6000fd5b505050506040513d602081101561246657600080fd5b50516001600160a01b031663867904b461247e6144de565b6001600160a01b031663eb1edd616040518163ffffffff1660e01b815260040160206040518083038186803b1580156124b657600080fd5b505afa1580156124ca573d6000803e3d6000fd5b505050506040513d60208110156124e057600080fd5b5051604080516001600160e01b031960e085901b1681526001600160a01b039092166004830152602482018b905251604480830192600092919082900301818387803b15801561252f57600080fd5b505af1158015612543573d6000803e3d6000fd5b5050505061254f6144de565b6001600160a01b03166322bf55ef886040518263ffffffff1660e01b815260040180828152602001915050600060405180830381600087803b15801561259457600080fd5b505af11580156125a8573d6000803e3d6000fd5b505050505b6125df60405180604001604052808e81526020018c8152506040518060400160405280868152602001858152506144f3565b6125e7611fe5565b6001600160a01b0316636c00f3108e8e898e8d8f6040518763ffffffff1660e01b815260040180876001600160a01b03166001600160a01b03168152602001868152602001858152602001848152602001838152602001826001600160a01b03166001600160a01b031681526020019650505050505050600060405180830381600087803b15801561267857600080fd5b505af115801561268c573d6000803e3d6000fd5b50505050612698611fe5565b6001600160a01b0316632f7206ce8e8e898e8d8f6040518763ffffffff1660e01b815260040180876001600160a01b03166001600160a01b03168152602001868152602001858152602001848152602001838152602001826001600160a01b03166001600160a01b031681526020019650505050505050600060405180830381600087803b15801561272957600080fd5b505af115801561273d573d6000803e3d6000fd5b505050505050505050505b9550959350505050565b60008211801561276a57506001600160a01b03811615155b8015612779575061277961337c565b15610801576127866147b1565b6001600160a01b03166321cad77483836040518363ffffffff1660e01b815260040180838152602001826001600160a01b03166001600160a01b0316815260200192505050600060405180830381600087803b1580156127e557600080fd5b505af11580156127f9573d6000803e3d6000fd5b505050505050565b612809611fe5565b6001600160a01b0316632d3169eb858585856040518563ffffffff1660e01b815260040180858152602001848152602001838152602001828152602001945050505050600060405180830381600087803b15801561286657600080fd5b505af115801561287a573d6000803e3d6000fd5b5050505050505050565b600061288e6147cd565b6001600160a01b03166323257c2b6d53797374656d53657474696e677360901b7f7072696365446576696174696f6e5468726573686f6c64466163746f720000006040518363ffffffff1660e01b8152600401808381526020018281526020019250505060206040518083038186803b15801561290a57600080fd5b505afa15801561291e573d6000803e3d6000fd5b505050506040513d602081101561293457600080fd5b5051905090565b6000610bc16524b9b9bab2b960d11b613860565b6000828201838110156106cc576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b6000610bc17044656c6567617465417070726f76616c7360781b613860565b6000806000808711612a0f576040805162461bcd60e51b815260206004820152600b60248201526a16995c9bc8185b5bdd5b9d60aa1b604482015290519081900360640190fd5b612a17615817565b612a1f613756565b6001600160a01b0316637a018a1e8a6040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b158015612a6257600080fd5b505afa158015612a76573d6000803e3d6000fd5b505050506040513d6020811015612a8c57600080fd5b505160a0820152612a9b613756565b6001600160a01b0316637a018a1e886040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b158015612ade57600080fd5b505afa158015612af2573d6000803e3d6000fd5b505050506040513d6020811015612b0857600080fd5b505160c08201526000612b1c898c8c613fd1565b905080612b35575060009350839250829150612ff09050565b612b3d613756565b6001600160a01b0316636ce66c808b838b8660a001518760c001516040518663ffffffff1660e01b8152600401808681526020018581526020018481526020018381526020018281526020019550505050505060606040518083038186803b158015612ba857600080fd5b505afa158015612bbc573d6000803e3d6000fd5b505050506040513d6060811015612bd257600080fd5b5080516020808301516040938401519186019190915284529083015260a082015160c0830151612c06918c918b91906147ea565b612c108a89614009565b15612c27575060009350839250829150612ff09050565b6000612c3d8b8a8560a001518660c00151614a2c565b606085019190915290508015612c60575060009450849350839250612ff0915050565b612c7283604001518460600151613771565b6040840151909650612c8a908763ffffffff61380316565b9450612c9b8b8d848c8a8d8d614200565b93506001600160a01b03841615612cb0578397505b8415612f0957612cbe613756565b6001600160a01b031663654a60ac8a87631cd554d160e21b6040518463ffffffff1660e01b815260040180848152602001838152602001828152602001935050505060206040518083038186803b158015612d1857600080fd5b505afa158015612d2c573d6000803e3d6000fd5b505050506040513d6020811015612d4257600080fd5b50519450612d4e61293b565b6001600160a01b03166332608039631cd554d160e21b6040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b158015612d9857600080fd5b505afa158015612dac573d6000803e3d6000fd5b505050506040513d6020811015612dc257600080fd5b50516001600160a01b031663867904b4612dda6144de565b6001600160a01b031663eb1edd616040518163ffffffff1660e01b815260040160206040518083038186803b158015612e1257600080fd5b505afa158015612e26573d6000803e3d6000fd5b505050506040513d6020811015612e3c57600080fd5b5051604080516001600160e01b031960e085901b1681526001600160a01b0390921660048301526024820189905251604480830192600092919082900301818387803b158015612e8b57600080fd5b505af1158015612e9f573d6000803e3d6000fd5b50505050612eab6144de565b6001600160a01b03166322bf55ef866040518263ffffffff1660e01b815260040180828152602001915050600060405180830381600087803b158015612ef057600080fd5b505af1158015612f04573d6000803e3d6000fd5b505050505b6040805180820182528c815260208082018c90528251808401909352855183528581015190830152612f3a916144f3565b612f42611fe5565b604080516306c00f3160e41b81526001600160a01b038f81166004830152602482018f905260448201869052606482018d9052608482018a90528b811660a483015291519290911691636c00f3109160c48082019260009290919082900301818387803b158015612fb257600080fd5b505af1158015612fc6573d6000803e3d6000fd5b505050506000612fd4612ffc565b1115612fec57612fec888c848c8a8860600151614a77565b5050505b96509650969350505050565b60006130066147cd565b6001600160a01b03166323257c2b6d53797374656d53657474696e677360901b7077616974696e67506572696f645365637360781b6040518363ffffffff1660e01b8152600401808381526020018281526020019250505060206040518083038186803b15801561290a57600080fd5b606080613081614c88565b60408051600a80825261016082019092529192506060919060208201610140803883390190505090506b53797374656d53746174757360a01b816000815181106130c757fe5b6020026020010181815250506c45786368616e6765537461746560981b816001815181106130f157fe5b6020026020010181815250506c45786368616e6765526174657360981b8160028151811061311b57fe5b602002602001018181525050680a6f2dce8d0cae8d2f60bb1b8160038151811061314157fe5b60200260200101818152505066119959541bdbdb60ca1b8160048151811061316557fe5b6020026020010181815250506d54726164696e675265776172647360901b8160058151811061319057fe5b6020026020010181815250507044656c6567617465417070726f76616c7360781b816006815181106131be57fe5b6020026020010181815250506524b9b9bab2b960d11b816007815181106131e157fe5b6020026020010181815250506844656274436163686560b81b8160088151811061320757fe5b6020026020010181815250507522bc31b430b733b2a1b4b931bab4ba213932b0b5b2b960511b8160098151811061132557fe5b6060815183510160405190808252806020026020018201604052801561326a578160200160208202803883390190505b50905060005b83518110156132ac5783818151811061328557fe5b602002602001015182828151811061329957fe5b6020908102919091010152600101613270565b5060005b82518110156132ef578281815181106132c557fe5b60200260200101518282865101815181106132dc57fe5b60209081029190910101526001016132b0565b5092915050565b60006133006147cd565b6001600160a01b03166323257c2b6d53797374656d53657474696e677360901b7f61746f6d69634d6178566f6c756d65506572426c6f636b0000000000000000006040518363ffffffff1660e01b8152600401808381526020018281526020019250505060206040518083038186803b15801561290a57600080fd5b60006133866147cd565b6001600160a01b031663d994502d6d53797374656d53657474696e677360901b741d1c98591a5b99d4995dd85c991cd15b98589b1959605a1b6040518363ffffffff1660e01b8152600401808381526020018281526020019250505060206040518083038186803b15801561290a57600080fd5b600080613405615854565b61340d614cd9565b905060008061341c8684614fcb565b9150915060008061342d8986614fcb565b9092509050613442848363ffffffff61294f16565b6060860151909750871180613457578761345d565b85606001515b97508781806134695750845b806134715750825b975097505050505050509250929050565b6000806000806000806000613495613756565b6001600160a01b031663055286e08a8c8b6040518463ffffffff1660e01b815260040180848152602001838152602001828152602001935050505060806040518083038186803b1580156134e857600080fd5b505afa1580156134fc573d6000803e3d6000fd5b505050506040513d608081101561351257600080fd5b50805160208201516040830151606090930151909650919450909250905061353a89896117ff565b94506135468186613771565b9650613558818863ffffffff61380316565b95505093975093979195509350565b808314156135b2576040805162461bcd60e51b8152602060048201526013602482015272086c2dc4ee840c4ca40e6c2daca40e6f2dce8d606b1b604482015290519081900360640190fd5b600082116135f5576040805162461bcd60e51b815260206004820152600b60248201526a16995c9bc8185b5bdd5b9d60aa1b604482015290519081900360640190fd5b6040805160028082526060808301845292602083019080388339019050509050838160008151811061362357fe5b602002602001018181525050818160018151811061363d57fe5b602002602001018181525050613651613756565b6001600160a01b0316630a7d36d1826040518263ffffffff1660e01b81526004018080602001828103825283818151815260200191508051906020019060200280838360005b838110156136af578181015183820152602001613697565b505050509050019250505060206040518083038186803b1580156136d257600080fd5b505afa1580156136e6573d6000803e3d6000fd5b505050506040513d60208110156136fc57600080fd5b505115613750576040805162461bcd60e51b815260206004820152601e60248201527f7372632f646573742072617465207374616c65206f7220666c61676765640000604482015290519081900360640190fd5b50505050565b6000610bc16c45786368616e6765526174657360981b613860565b60006106cc6137f683731a60e2e2a8be0bc2b6381dd31fd3fd5f9a28de4c63907af6c06040518163ffffffff1660e01b815260040160206040518083038186803b1580156137be57600080fd5b505af41580156137d2573d6000803e3d6000fd5b505050506040513d60208110156137e857600080fd5b50519063ffffffff61380316565b849063ffffffff61508a16565b60008282111561385a576040805162461bcd60e51b815260206004820152601e60248201527f536166654d6174683a207375627472616374696f6e206f766572666c6f770000604482015290519081900360640190fd5b50900390565b600081815260036020908152604080832054815170026b4b9b9b4b7339030b2323932b9b99d1607d1b9381019390935260318084018690528251808503909101815260519093019091526001600160a01b031690816132ef5760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b838110156139025781810151838201526020016138ea565b50505050905090810190601f16801561392f5780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b60006139476147cd565b6001600160a01b03166323257c2b6d53797374656d53657474696e677360901b7461746f6d696345786368616e67654665655261746560581b856040516020018083815260200182815260200192505050604051602081830303815290604052805190602001206040518363ffffffff1660e01b8152600401808381526020018281526020019250505060206040518083038186803b15801561129157600080fd5b60006139f36147cd565b6001600160a01b03166323257c2b6d53797374656d53657474696e677360901b6e65786368616e67654665655261746560881b856040516020018083815260200182815260200192505050604051602081830303815290604052805190602001206040518363ffffffff1660e01b8152600401808381526020018281526020019250505060206040518083038186803b15801561129157600080fd5b613a976157cc565b600080600080600080600080613aab611785565b6001600160a01b03166315987eb68d8d8d6040518463ffffffff1660e01b815260040180846001600160a01b03166001600160a01b0316815260200183815260200182815260200193505050506101006040518083038186803b158015613b1157600080fd5b505afa158015613b25573d6000803e3d6000fd5b505050506040513d610100811015613b3c57600080fd5b50805160208083015160408085015160608087015160808089015160a0808b015160c0808d015160e09d8e01518a5161010081018c529d8e529b8d019a909a52978b019690965293890192909252870152850152830152918101919091529c9b505050505050505050505050565b6000806000613bb7613756565b90506000613bc3612ffc565b9050816001600160a01b031663109e46a286600001518760c001518860a00151856040518563ffffffff1660e01b81526004018085815260200184815260200183815260200182815260200194505050505060206040518083038186803b158015613c2d57600080fd5b505afa158015613c41573d6000803e3d6000fd5b505050506040513d6020811015613c5757600080fd5b505160408087015160e088015160a0890151835163084f235160e11b81526004810193909352602483019190915260448201526064810184905290519195506001600160a01b0384169163109e46a291608480820192602092909190829003018186803b158015613cc757600080fd5b505afa158015613cdb573d6000803e3d6000fd5b505050506040513d6020811015613cf157600080fd5b50519395939450505050565b613d0561293b565b6001600160a01b03166332608039836040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b158015613d4857600080fd5b505afa158015613d5c573d6000803e3d6000fd5b505050506040513d6020811015613d7257600080fd5b505160408051632770a7eb60e21b81526001600160a01b0386811660048301526024820185905291519190921691639dc29fac91604480830192600092919082900301818387803b158015613dc657600080fd5b505af1158015613dda573d6000803e3d6000fd5b50505050613de6611fe5565b6001600160a01b031663ace88afd8484846040518463ffffffff1660e01b815260040180846001600160a01b03166001600160a01b031681526020018381526020018281526020019350505050600060405180830381600087803b158015613e4d57600080fd5b505af1158015613e61573d6000803e3d6000fd5b50505050505050565b613e7261293b565b6001600160a01b03166332608039836040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b158015613eb557600080fd5b505afa158015613ec9573d6000803e3d6000fd5b505050506040513d6020811015613edf57600080fd5b50516040805163219e412d60e21b81526001600160a01b038681166004830152602482018590529151919092169163867904b491604480830192600092919082900301818387803b158015613f3357600080fd5b505af1158015613f47573d6000803e3d6000fd5b50505050613f53611fe5565b6001600160a01b0316636f01a9868484846040518463ffffffff1660e01b815260040180846001600160a01b03166001600160a01b031681526020018381526020018281526020019350505050600060405180830381600087803b158015613e4d57600080fd5b6000610bc16844656274436163686560b81b613860565b6000806000613fe285856000611c60565b889550909350915050801561400057613ffd85858885610bc6565b92505b50509392505050565b6000631cd554d160e21b831461409757614021611861565b6001600160a01b031663a47af19e846040518263ffffffff1660e01b8152600401808281526020019150506040805180830381600087803b15801561406557600080fd5b505af1158015614079573d6000803e3d6000fd5b505050506040513d604081101561408f57600080fd5b506020015190505b631cd554d160e21b82146106cf5760006140af611861565b6001600160a01b031663a47af19e846040518263ffffffff1660e01b8152600401808281526020019150506040805180830381600087803b1580156140f357600080fd5b505af1158015614107573d6000803e3d6000fd5b505050506040513d604081101561411d57600080fd5b50602001519050818061183f57509392505050565b60045460009067ffffffffffffffff16421461414e578161416b565b60045461416b90600160401b90046001600160c01b03168361294f565b90506141756132f6565b8111156141c2576040805162461bcd60e51b815260206004820152601660248201527514dd5c9c185cdcd959081d9bdb1d5b59481b1a5b5a5d60521b604482015290519081900360640190fd5b600480546001600160c01b03909216600160401b0267ffffffffffffffff42811667ffffffffffffffff199094169390931790921691909117905550565b600061420a61293b565b6001600160a01b03166332608039896040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b15801561424d57600080fd5b505afa158015614261573d6000803e3d6000fd5b505050506040513d602081101561427757600080fd5b505160408051632770a7eb60e21b81526001600160a01b038a81166004830152602482018a905291519190921691639dc29fac91604480830192600092919082900301818387803b1580156142cb57600080fd5b505af11580156142df573d6000803e3d6000fd5b5050505060006142ed61293b565b6001600160a01b03166332608039876040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b15801561433057600080fd5b505afa158015614344573d6000803e3d6000fd5b505050506040513d602081101561435a57600080fd5b5051905082156144595760008190506143d9816001600160a01b031663ec5568896040518163ffffffff1660e01b815260040160206040518083038186803b1580156143a557600080fd5b505afa1580156143b9573d6000803e3d6000fd5b505050506040513d60208110156143cf57600080fd5b505186888a6150b4565b9250816001600160a01b031663867904b484886040518363ffffffff1660e01b815260040180836001600160a01b03166001600160a01b0316815260200182815260200192505050600060405180830381600087803b15801561443b57600080fd5b505af115801561444f573d6000803e3d6000fd5b50505050506144d2565b806001600160a01b031663867904b485876040518363ffffffff1660e01b815260040180836001600160a01b03166001600160a01b0316815260200182815260200192505050600060405180830381600087803b1580156144b957600080fd5b505af11580156144cd573d6000803e3d6000fd5b505050505b50979650505050505050565b6000610bc166119959541bdbdb60ca1b613860565b8151600090631cd554d160e21b148061451657506020830151631cd554d160e21b145b9050600081614526576003614529565b60025b60ff16905060608160405190808252806020026020018201604052801561455a578160200160208202803883390190505b50905084600060200201518160008151811061457257fe5b602090810291909101015284600160200201518160018151811061459257fe5b6020026020010181815250506060826040519080825280602002602001820160405280156145ca578160200160208202803883390190505b5090508460006020020151816000815181106145e257fe5b602090810291909101015284600160200201518160018151811061460257fe5b602002602001018181525050836146c057631cd554d160e21b8260028151811061462857fe5b602002602001018181525050731a60e2e2a8be0bc2b6381dd31fd3fd5f9a28de4c63907af6c06040518163ffffffff1660e01b815260040160206040518083038186803b15801561467857600080fd5b505af415801561468c573d6000803e3d6000fd5b505050506040513d60208110156146a257600080fd5b50518151829060029081106146b357fe5b6020026020010181815250505b6146c8613fba565b6001600160a01b03166317b38db483836040518363ffffffff1660e01b8152600401808060200180602001838103835285818151815260200191508051906020019060200280838360005b8381101561472b578181015183820152602001614713565b50505050905001838103825284818151815260200191508051906020019060200280838360005b8381101561476a578181015183820152602001614752565b50505050905001945050505050600060405180830381600087803b15801561479157600080fd5b505af11580156147a5573d6000803e3d6000fd5b50505050505050505050565b6000610bc16d54726164696e675265776172647360901b613860565b6000610bc16e466c657869626c6553746f7261676560881b613860565b82841415614835576040805162461bcd60e51b8152602060048201526013602482015272086c2dc4ee840c4ca40e6c2daca40e6f2dce8d606b1b604482015290519081900360640190fd5b6040805160028082526060808301845292602083019080388339019050509050848160008151811061486357fe5b602002602001018181525050838160018151811061487d57fe5b6020908102919091010152604080516002808252606082810190935281602001602082028038833901905050905083816000815181106148b957fe5b60200260200101818152505082816001815181106148d357fe5b6020026020010181815250506148e7613756565b6001600160a01b031663d89ee86183836040518363ffffffff1660e01b8152600401808060200180602001838103835285818151815260200191508051906020019060200280838360005b8381101561494a578181015183820152602001614932565b50505050905001838103825284818151815260200191508051906020019060200280838360005b83811015614989578181015183820152602001614971565b5050505090500194505050505060206040518083038186803b1580156149ae57600080fd5b505afa1580156149c2573d6000803e3d6000fd5b505050506040513d60208110156149d857600080fd5b5051156127f9576040805162461bcd60e51b815260206004820152601e60248201527f7372632f646573742072617465207374616c65206f7220666c61676765640000604482015290519081900360640190fd5b6000806000614a46614a3d876139e9565b611817896139e9565b90506000614a5688888888615249565b93509050614a6a828263ffffffff61294f16565b9350505094509492505050565b6000614a81613756565b90506000816001600160a01b0316637a018a1e886040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b158015614ac957600080fd5b505afa158015614add573d6000803e3d6000fd5b505050506040513d6020811015614af357600080fd5b505160408051633d00c50f60e11b81526004810188905290519192506000916001600160a01b03851691637a018a1e916024808301926020929190829003018186803b158015614b4257600080fd5b505afa158015614b56573d6000803e3d6000fd5b505050506040513d6020811015614b6c57600080fd5b50519050614b78611785565b60408051630f2a761760e21b81526001600160a01b038c81166004830152602482018c9052604482018b9052606482018a90526084820189905260a482018890524260c483015260e48201869052610104820185905291519290911691633ca9d85c916101248082019260009290919082900301818387803b158015614bfd57600080fd5b505af1158015614c11573d6000803e3d6000fd5b5050604080518b8152602081018b90528082018a9052606081018990526080810188905260a0810186905260c0810185905290516001600160a01b038d1693507f62e40d554c7abcdd31074960d8347a2225daeb04d93bc748f049ba2ce946239892509081900360e00190a2505050505050505050565b604080516001808252818301909252606091602080830190803883390190505090506e466c657869626c6553746f7261676560881b81600081518110614cca57fe5b60200260200101818152505090565b614ce1615854565b60408051600480825260a08201909252606091602082016080803883390190505090507f65786368616e676544796e616d69634665655468726573686f6c64000000000081600081518110614d3257fe5b6020026020010181815250507f65786368616e676544796e616d6963466565576569676874446563617900000081600181518110614d6c57fe5b6020026020010181815250507f65786368616e676544796e616d6963466565526f756e6473000000000000000081600281518110614da657fe5b6020026020010181815250507465786368616e67654d617844796e616d696346656560581b81600381518110614dd857fe5b6020026020010181815250506060614dee6147cd565b6001600160a01b031663b67fa7ed6d53797374656d53657474696e677360901b846040518363ffffffff1660e01b81526004018083815260200180602001828103825283818151815260200191508051906020019060200280838360005b83811015614e64578181015183820152602001614e4c565b50505050905001935050505060006040518083038186803b158015614e8857600080fd5b505afa158015614e9c573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526020811015614ec557600080fd5b8101908080516040519392919084640100000000821115614ee557600080fd5b908301906020820185811115614efa57600080fd5b8251866020820283011164010000000082111715614f1757600080fd5b82525081516020918201928201910280838360005b83811015614f44578181015183820152602001614f2c565b505050509050016040525050509050604051806080016040528082600081518110614f6b57fe5b6020026020010151815260200182600181518110614f8557fe5b6020026020010151815260200182600281518110614f9f57fe5b6020026020010151815260200182600381518110614fb957fe5b60200260200101518152509250505090565b600080631cd554d160e21b841480614fe857506001836040015111155b15614ff857506000905080611399565b6000615002613756565b6001600160a01b0316637a018a1e866040518263ffffffff1660e01b81526004018082815260200191505060206040518083038186803b15801561504557600080fd5b505afa158015615059573d6000803e3d6000fd5b505050506040513d602081101561506f57600080fd5b5051905061507e8582866152d5565b92509250509250929050565b6000670de0b6b3a76400006150a5848463ffffffff6154f516565b816150ac57fe5b049392505050565b6000606960f81b6001600160f81b031983831a60f81b16141561511e576040805162461bcd60e51b815260206004820152601c60248201527f43616e6e6f74207669727475616c697a6520746869732073796e746800000000604482015290519081900360640190fd5b600061516661512b61554e565b6040518060400160405280601b81526020017f436f756c64206e6f7420637265617465206e6577207653796e74680000000000815250615572565b60025460408051638d7017d360e01b81526001600160a01b038a81166004830152928316602482015288831660448201526064810188905260848101879052905192935090831691638d7017d39160a48082019260009290919082900301818387803b1580156151d557600080fd5b505af11580156151e9573d6000803e3d6000fd5b5050604080516001600160a01b038581168252602082018890528183018990529151828a169450918a1692507fb5ec76d79549c775883022e4426db5cd36bd5307f216cdb341554c301548ef9f919081900360600190a395945050505050565b600080615254615854565b61525c614cd9565b905060008061526c8887856152d5565b9150915060008061527e8b8a876152d5565b9092509050615293848363ffffffff61294f16565b60608601519097508711806152a857876152ae565b85606001515b97508781806152ba5750845b806152c25750825b9750975050505050505094509492505050565b600080631cd554d160e21b8514806152f257506001836040015111155b15615302575060009050806154ed565b606061530c613756565b6001600160a01b031663ed762450878660400151886040518463ffffffff1660e01b815260040180848152602001838152602001828152602001935050505060006040518083038186803b15801561536357600080fd5b505afa158015615377573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160409081528110156153a057600080fd5b81019080805160405193929190846401000000008211156153c057600080fd5b9083019060208201858111156153d557600080fd5b82518660208202830111640100000000821117156153f257600080fd5b82525081516020918201928201910280838360005b8381101561541f578181015183820152602001615407565b505050509050016040526020018051604051939291908464010000000082111561544857600080fd5b90830190602082018581111561545d57600080fd5b825186602082028301116401000000008211171561547a57600080fd5b82525081516020918201928201910280838360005b838110156154a757818101518382015260200161548f565b5050505090500160405250505050809150506154cc81856000015186602001516155ea565b60608501519093508311806154e157836154e7565b84606001515b93509150505b935093915050565b600082615504575060006106cf565b8282028284828161551157fe5b04146106cc5760405162461bcd60e51b81526004018080602001828103825260218152602001806159606021913960400191505060405180910390fd5b6000610bc1755669727475616c53796e74684d6173746572636f707960501b613860565b6000606061557f84615674565b90506037602082016000f09150826001600160a01b0383166155e25760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156139025781810151838201526020016138ea565b505092915050565b60008351600014156155fe575060006117f8565b8351600090600019015b8015610cdd5761561e828563ffffffff61508a16565b9150600061565687600184038151811061563457fe5b602002602001015188848151811061564857fe5b6020026020010151886156e4565b9050615668838263ffffffff61294f16565b92505060001901615608565b60408051693d602d80600a3d3981f360b01b602082015269363d3d373d3d3d363d7360b01b602a82015260609290921b6bffffffffffffffffffffffff191660348301526e5af43d82803e903d91602b57fd5bf360881b6048830152805180830360370181526057909201905290565b6000826156f3575060006117f8565b600083851161570457848403615708565b8385035b9050600061571c828663ffffffff61573816565b905083811161572c576000613ffd565b92909203949350505050565b60006106cc8261575685670de0b6b3a764000063ffffffff6154f516565b9063ffffffff61576216565b60008082116157b8576040805162461bcd60e51b815260206004820152601a60248201527f536166654d6174683a206469766973696f6e206279207a65726f000000000000604482015290519081900360640190fd5b60008284816157c357fe5b04949350505050565b60405180610100016040528060008019168152602001600081526020016000801916815260200160008152602001600081526020016000815260200160008152602001600081525090565b6040518060e00160405280600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525090565b604051806080016040528060008152602001600081526020016000815260200160008152509056fe596f75206d757374206265206e6f6d696e61746564206265666f726520796f752063616e20616363657074206f776e65727368697054686520616d6f756e742072656365697665642069732062656c6f7720746865206d696e696d756d20616d6f756e74207370656369666965642e45786368616e6765723a204f6e6c792073796e746865746978206f7220612073796e746820636f6e74726163742063616e20706572666f726d207468697320616374696f6e4f6e6c792074686520636f6e7472616374206f776e6572206d617920706572666f726d207468697320616374696f6e536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f7743616e6e6f7420736574746c6520647572696e672077616974696e6720706572696f64a265627a7a72315820d3cc996c4b5f085a856d8100bbbf0a8feb79df26ea89b1f37fd5c2fed358865264736f6c6343000510003200000000000000000000000073570075092502472e4b61a7058df1a4a1db12f2000000000000000000000000242a3df52c375bee81b1c668741d7c63af68fdd2

Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)

00000000000000000000000073570075092502472e4b61a7058df1a4a1db12f2000000000000000000000000242a3df52c375bee81b1c668741d7c63af68fdd2

-----Decoded View---------------
Arg [0] : _owner (address): 0x73570075092502472e4b61a7058df1a4a1db12f2
Arg [1] : _resolver (address): 0x242a3df52c375bee81b1c668741d7c63af68fdd2

-----Encoded View---------------
2 Constructor Arguments found :
Arg [0] : 00000000000000000000000073570075092502472e4b61a7058df1a4a1db12f2
Arg [1] : 000000000000000000000000242a3df52c375bee81b1c668741d7c63af68fdd2


Block Transaction Difficulty Gas Used Reward
Block Uncle Number Difficulty Gas Used Reward
Loading

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.