ETH Price: $2,046.81 (+0.92%)

Transaction Decoder

Block:
10645429 at Aug-12-2020 01:37:04 PM +UTC
Transaction Fee:
0.013923665 ETH $28.50
Gas Used:
67,102 Gas / 207.5 Gwei

Emitted Events:

242 MKernel.0xc046229e00000000000000000000000000000000000000000000000000000000( 0xc046229e00000000000000000000000000000000000000000000000000000000, 0x00000000000000000000000024f392f59df620024d0437d31e4feaf74e88d114, 0x62ee148286ad6d5bbde85852daed8ecc55982844b5e2ad6e9f84c26c09ce5c66, 0x000000000000000000000000521550e569bc80f1b4957c4f3fd3d677d9ca31f1, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000004000000000, 000000000000000000000000000000000000000000000000000000a4c046229e, 62ee148286ad6d5bbde85852daed8ecc55982844b5e2ad6e9f84c26c09ce5c66, 000000000000000000000000521550e569bc80f1b4957c4f3fd3d677d9ca31f1, 0000000000000000000000000000000000000000000000000000000000e222a6, 0000000000000000000000000000000000000000000000000000000000e222a6, 0000000000000000000000000000000000000000000000000000000000000000 )
243 MKernel.LogErrorWithHintBytes32( bytes32Value=62EE148286AD6D5BBDE85852DAED8ECC55982844B5E2AD6E9F84C26C09CE5C66, methodSig=MKernel::processTradeForStopProfit, errMsg=NO_ACTION_PERFORMED )

Account State Difference:

  Address   Before After State Difference Code
0x24f392F5...74E88d114
4.16893224975 Eth
Nonce: 3300
4.15500858475 Eth
Nonce: 3301
0.013923665
(MiningPoolHub)
5,229.292431491771470242 Eth5,229.306355156771470242 Eth0.013923665

Execution Trace

MKernel.processTradeForStopProfit( _orderHash=62EE148286AD6D5BBDE85852DAED8ECC55982844B5E2AD6E9F84C26C09CE5C66, _exchangeConnector=0x521550E569bc80F1b4957C4F3Fd3D677d9ca31f1, _tokenPrices=[14820006, 14820006], _bufferInPrincipal=0 )
  • Config.isAdminValid( _admin=0x24f392F59DF620024D0437d31e4fEaf74E88d114 ) => ( True )
  • LinkToken.balanceOf( _owner=0xaf38668F4719Ecf9452DC0300BE3f6c83Cbf3721 ) => ( balance=50936971287705210910798 )
  • LinkToken.balanceOf( _owner=0xaf38668F4719Ecf9452DC0300BE3f6c83Cbf3721 ) => ( balance=50936971287705210910798 )
    processTradeForStopProfit[MKernel (ln:2245)]
    File 1 of 3: MKernel
    pragma solidity 0.4.24;
    
    contract DSAuthority {
        function canCall(address src, address dst, bytes4 sig) public view returns (bool);
    }
    
    
    contract DSAuthEvents {
        event LogSetAuthority (address indexed authority);
        event LogSetOwner     (address indexed owner);
    }
    
    
    contract DSAuth is DSAuthEvents {
        DSAuthority  public  authority;
        address      public  owner;
    
        constructor() public {
            owner = msg.sender;
            emit LogSetOwner(msg.sender);
        }
    
        function setOwner(address owner_)
            public
            auth
        {
            owner = owner_;
            emit LogSetOwner(owner);
        }
    
        function setAuthority(DSAuthority authority_)
            public
            auth
        {
            authority = authority_;
            emit LogSetAuthority(authority);
        }
    
        modifier auth {
            require(isAuthorized(msg.sender, msg.sig), "DSAuth::_ SENDER_NOT_AUTHORIZED");
            _;
        }
    
        function isAuthorized(address src, bytes4 sig) internal view returns (bool) {
            if (src == address(this)) {
                return true;
            } else if (src == owner) {
                return true;
            } else if (authority == DSAuthority(0)) {
                return false;
            } else {
                return authority.canCall(src, this, sig);
            }
        }
    }
    
    
    contract Proxy {
    
        // masterCopy always needs to be first declared variable, to ensure that it is at the same location in the contracts to which calls are delegated.
        address masterCopy;
    
        /// @dev Constructor function sets address of master copy contract.
        /// @param _masterCopy Master copy address.
        constructor(address _masterCopy)
            public
        {
            require(_masterCopy != 0, "Invalid master copy address provided");
            masterCopy = _masterCopy;
        }
    
        /// @dev Fallback function forwards all transactions and returns all received return data.
        function ()
            external
            payable
        {
            // solium-disable-next-line security/no-inline-assembly
            assembly {
                let masterCopy := and(sload(0), 0xffffffffffffffffffffffffffffffffffffffff)
                calldatacopy(0, 0, calldatasize())
                let success := delegatecall(gas, masterCopy, 0, calldatasize(), 0, 0)
                returndatacopy(0, 0, returndatasize())
                if eq(success, 0) { revert(0, returndatasize()) }
                return(0, returndatasize())
            }
        }
    
        function implementation()
            public
            view
            returns (address)
        {
            return masterCopy;
        }
    
        function proxyType()
            public
            pure
            returns (uint256)
        {
            return 2;
        }
    }
    
    
    contract ErrorUtils {
    
        event LogError(string methodSig, string errMsg);
        event LogErrorWithHintBytes32(bytes32 indexed bytes32Value, string methodSig, string errMsg);
        event LogErrorWithHintAddress(address indexed addressValue, string methodSig, string errMsg);
    
    }
    
    
    contract DSMath {
        function add(uint x, uint y) internal pure returns (uint z) {
            require((z = x + y) >= x);
        }
        function sub(uint x, uint y) internal pure returns (uint z) {
            require((z = x - y) <= x);
        }
        function mul(uint x, uint y) internal pure returns (uint z) {
            require(y == 0 || (z = x * y) / y == x);
        }
    
        // custom : not in original DSMath, putting it here for consistency, copied from SafeMath
        function div(uint x, uint y) internal pure returns (uint z) {
            z = x / y;
        }
    
        function min(uint x, uint y) internal pure returns (uint z) {
            return x <= y ? x : y;
        }
        function max(uint x, uint y) internal pure returns (uint z) {
            return x >= y ? x : y;
        }
        function imin(int x, int y) internal pure returns (int z) {
            return x <= y ? x : y;
        }
        function imax(int x, int y) internal pure returns (int z) {
            return x >= y ? x : y;
        }
    
        uint constant WAD = 10 ** 18;
        uint constant RAY = 10 ** 27;
    
        function wmul(uint x, uint y) internal pure returns (uint z) {
            z = add(mul(x, y), WAD / 2) / WAD;
        }
        function rmul(uint x, uint y) internal pure returns (uint z) {
            z = add(mul(x, y), RAY / 2) / RAY;
        }
        function wdiv(uint x, uint y) internal pure returns (uint z) {
            z = add(mul(x, WAD), y / 2) / y;
        }
        function rdiv(uint x, uint y) internal pure returns (uint z) {
            z = add(mul(x, RAY), y / 2) / y;
        }
    
        // This famous algorithm is called "exponentiation by squaring"
        // and calculates x^n with x as fixed-point and n as regular unsigned.
        //
        // It's O(log n), instead of O(n) for naive repeated multiplication.
        //
        // These facts are why it works:
        //
        //  If n is even, then x^n = (x^2)^(n/2).
        //  If n is odd,  then x^n = x * x^(n-1),
        //   and applying the equation for even x gives
        //    x^n = x * (x^2)^((n-1) / 2).
        //
        //  Also, EVM division is flooring and
        //    floor[(n-1) / 2] = floor[n / 2].
        //
        function rpow(uint x, uint n) internal pure returns (uint z) {
            z = n % 2 != 0 ? x : RAY;
    
            for (n /= 2; n != 0; n /= 2) {
                x = rmul(x, x);
    
                if (n % 2 != 0) {
                    z = rmul(z, x);
                }
            }
        }
    }
    
    
    contract SelfAuthorized {
        modifier authorized() {
            require(msg.sender == address(this), "Method can only be called from this contract");
            _;
        }
    }
    
    
    contract DSNote {
        event LogNote(
            bytes4   indexed  sig,
            address  indexed  guy,
            bytes32  indexed  foo,
            bytes32  indexed  bar,
            uint              wad,
            bytes             fax
        ) anonymous;
    
        modifier note {
            bytes32 foo;
            bytes32 bar;
    
            assembly {
                foo := calldataload(4)
                bar := calldataload(36)
            }
    
            emit LogNote(msg.sig, msg.sender, foo, bar, msg.value, msg.data);
    
            _;
        }
    }
    
    
    contract Utils {
    
        modifier addressValid(address _address) {
            require(_address != address(0), "Utils::_ INVALID_ADDRESS");
            _;
        }
    
    }
    
    
    contract WETH9 {
        string public name     = "Wrapped Ether";
        string public symbol   = "WETH";
        uint8  public decimals = 18;
    
        event  Approval(address indexed _owner, address indexed _spender, uint _value);
        event  Transfer(address indexed _from, address indexed _to, uint _value);
        event  Deposit(address indexed _owner, uint _value);
        event  Withdrawal(address indexed _owner, uint _value);
    
        mapping (address => uint)                       public  balanceOf;
        mapping (address => mapping (address => uint))  public  allowance;
    
        function() public payable {
            deposit();
        }
    
        function deposit() public payable {
            balanceOf[msg.sender] += msg.value;
            Deposit(msg.sender, msg.value);
        }
    
        function withdraw(uint wad) public {
            require(balanceOf[msg.sender] >= wad);
            balanceOf[msg.sender] -= wad;
            msg.sender.transfer(wad);
            Withdrawal(msg.sender, wad);
        }
    
        function totalSupply() public view returns (uint) {
            return this.balance;
        }
    
        function approve(address guy, uint wad) public returns (bool) {
            allowance[msg.sender][guy] = wad;
            Approval(msg.sender, guy, wad);
            return true;
        }
    
        function transfer(address dst, uint wad) public returns (bool) {
            return transferFrom(msg.sender, dst, wad);
        }
    
        function transferFrom(address src, address dst, uint wad)
            public
            returns (bool)
        {
            require(balanceOf[src] >= wad);
    
            if (src != msg.sender && allowance[src][msg.sender] != uint(-1)) {
                require(allowance[src][msg.sender] >= wad);
                allowance[src][msg.sender] -= wad;
            }
    
            balanceOf[src] -= wad;
            balanceOf[dst] += wad;
    
            Transfer(src, dst, wad);
    
            return true;
        }
    }
    
    
    contract DateTime {
        /*
            *  Date and Time utilities for ethereum contracts
            *
            */
        struct _DateTime {
            uint16 year;
            uint8 month;
            uint8 day;
            uint8 hour;
            uint8 minute;
            uint8 second;
            uint8 weekday;
        }
    
        uint constant DAY_IN_SECONDS = 86400;
        uint constant YEAR_IN_SECONDS = 31536000;
        uint constant LEAP_YEAR_IN_SECONDS = 31622400;
    
        uint constant HOUR_IN_SECONDS = 3600;
        uint constant MINUTE_IN_SECONDS = 60;
    
        uint16 constant ORIGIN_YEAR = 1970;
    
        function isLeapYear(uint16 year) public pure returns (bool) {
            if (year % 4 != 0) {
                return false;
            }
            if (year % 100 != 0) {
                return true;
            }
            if (year % 400 != 0) {
                return false;
            }
            return true;
        }
    
        function leapYearsBefore(uint year) public pure returns (uint) {
            year -= 1;
            return year / 4 - year / 100 + year / 400;
        }
    
        function getDaysInMonth(uint8 month, uint16 year) public pure returns (uint8) {
            if (month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) {
                return 31;
            }
            else if (month == 4 || month == 6 || month == 9 || month == 11) {
                return 30;
            }
            else if (isLeapYear(year)) {
                return 29;
            }
            else {
                return 28;
            }
        }
    
        function parseTimestamp(uint timestamp) internal pure returns (_DateTime dt) {
            uint secondsAccountedFor = 0;
            uint buf;
            uint8 i;
    
            // Year
            dt.year = getYear(timestamp);
            buf = leapYearsBefore(dt.year) - leapYearsBefore(ORIGIN_YEAR);
    
            secondsAccountedFor += LEAP_YEAR_IN_SECONDS * buf;
            secondsAccountedFor += YEAR_IN_SECONDS * (dt.year - ORIGIN_YEAR - buf);
    
            // Month
            uint secondsInMonth;
            for (i = 1; i <= 12; i++) {
                secondsInMonth = DAY_IN_SECONDS * getDaysInMonth(i, dt.year);
                if (secondsInMonth + secondsAccountedFor > timestamp) {
                    dt.month = i;
                    break;
                }
                secondsAccountedFor += secondsInMonth;
            }
    
            // Day
            for (i = 1; i <= getDaysInMonth(dt.month, dt.year); i++) {
                if (DAY_IN_SECONDS + secondsAccountedFor > timestamp) {
                    dt.day = i;
                    break;
                }
                secondsAccountedFor += DAY_IN_SECONDS;
            }
    
            // Hour
            dt.hour = getHour(timestamp);
    
            // Minute
            dt.minute = getMinute(timestamp);
    
            // Second
            dt.second = getSecond(timestamp);
    
            // Day of week.
            dt.weekday = getWeekday(timestamp);
        }
    
        function getYear(uint timestamp) public pure returns (uint16) {
            uint secondsAccountedFor = 0;
            uint16 year;
            uint numLeapYears;
    
            // Year
            year = uint16(ORIGIN_YEAR + timestamp / YEAR_IN_SECONDS);
            numLeapYears = leapYearsBefore(year) - leapYearsBefore(ORIGIN_YEAR);
    
            secondsAccountedFor += LEAP_YEAR_IN_SECONDS * numLeapYears;
            secondsAccountedFor += YEAR_IN_SECONDS * (year - ORIGIN_YEAR - numLeapYears);
    
            while (secondsAccountedFor > timestamp) {
                if (isLeapYear(uint16(year - 1))) {
                    secondsAccountedFor -= LEAP_YEAR_IN_SECONDS;
                }
                else {
                    secondsAccountedFor -= YEAR_IN_SECONDS;
                }
                year -= 1;
            }
            return year;
        }
    
        function getMonth(uint timestamp) public pure returns (uint8) {
            return parseTimestamp(timestamp).month;
        }
    
        function getDay(uint timestamp) public pure returns (uint8) {
            return parseTimestamp(timestamp).day;
        }
    
        function getHour(uint timestamp) public pure returns (uint8) {
            return uint8((timestamp / 60 / 60) % 24);
        }
    
        function getMinute(uint timestamp) public pure returns (uint8) {
            return uint8((timestamp / 60) % 60);
        }
    
        function getSecond(uint timestamp) public pure returns (uint8) {
            return uint8(timestamp % 60);
        }
    
        function getWeekday(uint timestamp) public pure returns (uint8) {
            return uint8((timestamp / DAY_IN_SECONDS + 4) % 7);
        }
    
        function toTimestamp(uint16 year, uint8 month, uint8 day) public pure returns (uint timestamp) {
            return toTimestamp(year, month, day, 0, 0, 0);
        }
    
        function toTimestamp(uint16 year, uint8 month, uint8 day, uint8 hour) public pure returns (uint timestamp) {
            return toTimestamp(year, month, day, hour, 0, 0);
        }
    
        function toTimestamp(uint16 year, uint8 month, uint8 day, uint8 hour, uint8 minute) public pure returns (uint timestamp) {
            return toTimestamp(year, month, day, hour, minute, 0);
        }
    
        function toTimestamp(uint16 year, uint8 month, uint8 day, uint8 hour, uint8 minute, uint8 second) public pure returns (uint timestamp) {
            uint16 i;
    
            // Year
            for (i = ORIGIN_YEAR; i < year; i++) {
                if (isLeapYear(i)) {
                    timestamp += LEAP_YEAR_IN_SECONDS;
                }
                else {
                    timestamp += YEAR_IN_SECONDS;
                }
            }
    
            // Month
            uint8[12] memory monthDayCounts;
            monthDayCounts[0] = 31;
            if (isLeapYear(year)) {
                monthDayCounts[1] = 29;
            }
            else {
                monthDayCounts[1] = 28;
            }
            monthDayCounts[2] = 31;
            monthDayCounts[3] = 30;
            monthDayCounts[4] = 31;
            monthDayCounts[5] = 30;
            monthDayCounts[6] = 31;
            monthDayCounts[7] = 31;
            monthDayCounts[8] = 30;
            monthDayCounts[9] = 31;
            monthDayCounts[10] = 30;
            monthDayCounts[11] = 31;
    
            for (i = 1; i < month; i++) {
                timestamp += DAY_IN_SECONDS * monthDayCounts[i - 1];
            }
    
            // Day
            timestamp += DAY_IN_SECONDS * (day - 1);
    
            // Hour
            timestamp += HOUR_IN_SECONDS * (hour);
    
            // Minute
            timestamp += MINUTE_IN_SECONDS * (minute);
    
            // Second
            timestamp += second;
    
            return timestamp;
        }
    }
    
    
    interface ERC20 {
    
        function name() external view returns(string);
        function symbol() external view returns(string);
        function decimals() external view returns(uint8);
        function totalSupply() external view returns (uint);
    
        function balanceOf(address tokenOwner) external view returns (uint balance);
        function allowance(address tokenOwner, address spender) external view returns (uint remaining);
        function transfer(address to, uint tokens) external returns (bool success);
        function approve(address spender, uint tokens) external returns (bool success);
        function transferFrom(address from, address to, uint tokens) external returns (bool success);
    
        event Transfer(address indexed from, address indexed to, uint tokens);
        event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
    }
    
    
    contract MasterCopy is SelfAuthorized {
      // masterCopy always needs to be first declared variable, to ensure that it is at the same location as in the Proxy contract.
      // It should also always be ensured that the address is stored alone (uses a full word)
        address masterCopy;
    
      /// @dev Allows to upgrade the contract. This can only be done via a Safe transaction.
      /// @param _masterCopy New contract address.
        function changeMasterCopy(address _masterCopy)
            public
            authorized
        {
            // Master copy address cannot be null.
            require(_masterCopy != 0, "Invalid master copy address provided");
            masterCopy = _masterCopy;
        }
    }
    
    
    contract Config is DSNote, DSAuth, Utils {
    
        WETH9 public weth9;
        mapping (address => bool) public isAccountHandler;
        mapping (address => bool) public isAdmin;
        address[] public admins;
        bool public disableAdminControl = false;
        
        event LogAdminAdded(address indexed _admin, address _by);
        event LogAdminRemoved(address indexed _admin, address _by);
    
        constructor() public {
            admins.push(msg.sender);
            isAdmin[msg.sender] = true;
        }
    
        modifier onlyAdmin(){
            require(isAdmin[msg.sender], "Config::_ SENDER_NOT_AUTHORIZED");
            _;
        }
    
        function setWETH9
        (
            address _weth9
        ) 
            public
            auth
            note
            addressValid(_weth9) 
        {
            weth9 = WETH9(_weth9);
        }
    
        function setAccountHandler
        (
            address _accountHandler,
            bool _isAccountHandler
        )
            public
            auth
            note
            addressValid(_accountHandler)
        {
            isAccountHandler[_accountHandler] = _isAccountHandler;
        }
    
        function toggleAdminsControl() 
            public
            auth
            note
        {
            disableAdminControl = !disableAdminControl;
        }
    
        function isAdminValid(address _admin)
            public
            view
            returns (bool)
        {
            if(disableAdminControl) {
                return true;
            } else {
                return isAdmin[_admin];
            }
        }
    
        function getAllAdmins()
            public
            view
            returns(address[])
        {
            return admins;
        }
    
        function addAdmin
        (
            address _admin
        )
            external
            note
            onlyAdmin
            addressValid(_admin)
        {   
            require(!isAdmin[_admin], "Config::addAdmin ADMIN_ALREADY_EXISTS");
    
            admins.push(_admin);
            isAdmin[_admin] = true;
    
            emit LogAdminAdded(_admin, msg.sender);
        }
    
        function removeAdmin
        (
            address _admin
        ) 
            external
            note
            onlyAdmin
            addressValid(_admin)
        {   
            require(isAdmin[_admin], "Config::removeAdmin ADMIN_DOES_NOT_EXIST");
            require(msg.sender != _admin, "Config::removeAdmin ADMIN_NOT_AUTHORIZED");
    
            isAdmin[_admin] = false;
    
            for (uint i = 0; i < admins.length - 1; i++) {
                if (admins[i] == _admin) {
                    admins[i] = admins[admins.length - 1];
                    admins.length -= 1;
                    break;
                }
            }
    
            emit LogAdminRemoved(_admin, msg.sender);
        }
    }
    
    
    library ECRecovery {
    
        function recover(bytes32 _hash, bytes _sig)
            internal
            pure
        returns (address)
        {
            bytes32 r;
            bytes32 s;
            uint8 v;
    
            if (_sig.length != 65) {
                return (address(0));
            }
    
            assembly {
                r := mload(add(_sig, 32))
                s := mload(add(_sig, 64))
                v := byte(0, mload(add(_sig, 96)))
            }
    
            if (v < 27) {
                v += 27;
            }
    
            if (v != 27 && v != 28) {
                return (address(0));
            } else {
                return ecrecover(_hash, v, r, s);
            }
        }
    
        function toEthSignedMessageHash(bytes32 _hash)
            internal
            pure
        returns (bytes32)
        {
            return keccak256(
                abi.encodePacked("\x19Ethereum Signed Message:\n32", _hash)
            );
        }
    }
    
    
    contract Utils2 {
        using ECRecovery for bytes32;
        
        function _recoverSigner(bytes32 _hash, bytes _signature) 
            internal
            pure
            returns(address _signer)
        {
            return _hash.toEthSignedMessageHash().recover(_signature);
        }
    
    }
    
    
    contract DSThing is DSNote, DSAuth, DSMath {
    
        function S(string s) internal pure returns (bytes4) {
            return bytes4(keccak256(s));
        }
    
    }
    
    
    contract DSStop is DSNote, DSAuth {
    
        bool public stopped = false;
    
        modifier whenNotStopped {
            require(!stopped, "DSStop::_ FEATURE_STOPPED");
            _;
        }
    
        modifier whenStopped {
            require(stopped, "DSStop::_ FEATURE_NOT_STOPPED");
            _;
        }
    
        function stop() public auth note {
            stopped = true;
        }
        function start() public auth note {
            stopped = false;
        }
    
    }
    
    
    contract Account is MasterCopy, DSNote, Utils, Utils2, ErrorUtils {
    
        address[] public users;
        mapping (address => bool) public isUser;
        mapping (bytes32 => bool) public actionCompleted;
    
        WETH9 public weth9;
        Config public config;
        bool public isInitialized = false;
    
        event LogTransferBySystem(address indexed token, address indexed to, uint value, address by);
        event LogTransferByUser(address indexed token, address indexed to, uint value, address by);
        event LogUserAdded(address indexed user, address by);
        event LogUserRemoved(address indexed user, address by);
        event LogImplChanged(address indexed newImpl, address indexed oldImpl);
    
        modifier initialized() {
            require(isInitialized, "Account::_ ACCOUNT_NOT_INITIALIZED");
            _;
        }
    
        modifier notInitialized() {
            require(!isInitialized, "Account::_ ACCOUNT_ALREADY_INITIALIZED");
            _;
        }
    
        modifier userExists(address _user) {
            require(isUser[_user], "Account::_ INVALID_USER");
            _;
        }
    
        modifier userDoesNotExist(address _user) {
            require(!isUser[_user], "Account::_ USER_DOES_NOT_EXISTS");
            _;
        }
    
        modifier onlyAdmin() {
            require(config.isAdminValid(msg.sender), "Account::_ INVALID_ADMIN_ACCOUNT");
            _;
        }
    
        modifier onlyHandler(){
            require(config.isAccountHandler(msg.sender), "Account::_ INVALID_ACC_HANDLER");
            _;
        }
    
        function init(address _user, address _config)
            public 
            notInitialized
        {
            users.push(_user);
            isUser[_user] = true;
            config = Config(_config);
            weth9 = config.weth9();
            isInitialized = true;
        }
        
        function getAllUsers() public view returns (address[]) {
            return users;
        }
    
        function balanceFor(address _token) public view returns (uint _balance){
            _balance = ERC20(_token).balanceOf(this);
        }
        
        function transferBySystem
        (   
            address _token,
            address _to,
            uint _value
        ) 
            external 
            onlyHandler
            note 
            initialized
        {
            require(ERC20(_token).balanceOf(this) >= _value, "Account::transferBySystem INSUFFICIENT_BALANCE_IN_ACCOUNT");
            ERC20(_token).transfer(_to, _value);
    
            emit LogTransferBySystem(_token, _to, _value, msg.sender);
        }
        
        function transferByUser
        (   
            address _token,
            address _to,
            uint _value,
            uint _salt,
            bytes _signature
        )
            external
            addressValid(_to)
            note
            initialized
            onlyAdmin
        {
            bytes32 actionHash = _getTransferActionHash(_token, _to, _value, _salt);
    
            if(actionCompleted[actionHash]) {
                emit LogError("Account::transferByUser", "ACTION_ALREADY_PERFORMED");
                return;
            }
    
            if(ERC20(_token).balanceOf(this) < _value){
                emit LogError("Account::transferByUser", "INSUFFICIENT_BALANCE_IN_ACCOUNT");
                return;
            }
    
            address signer = _recoverSigner(actionHash, _signature);
    
            if(!isUser[signer]) {
                emit LogError("Account::transferByUser", "SIGNER_NOT_AUTHORIZED_WITH_ACCOUNT");
                return;
            }
    
            actionCompleted[actionHash] = true;
            
            if (_token == address(weth9)) {
                weth9.withdraw(_value);
                _to.transfer(_value);
            } else {
                require(ERC20(_token).transfer(_to, _value), "Account::transferByUser TOKEN_TRANSFER_FAILED");
            }
    
            emit LogTransferByUser(_token, _to, _value, signer);
        }
    
        function addUser
        (
            address _user,
            uint _salt,
            bytes _signature
        )
            external 
            note 
            addressValid(_user)
            userDoesNotExist(_user)
            initialized
            onlyAdmin
        {   
            bytes32 actionHash = _getUserActionHash(_user, "ADD_USER", _salt);
            if(actionCompleted[actionHash])
            {
                emit LogError("Account::addUser", "ACTION_ALREADY_PERFORMED");
                return;
            }
    
            address signer = _recoverSigner(actionHash, _signature);
    
            if(!isUser[signer]) {
                emit LogError("Account::addUser", "SIGNER_NOT_AUTHORIZED_WITH_ACCOUNT");
                return;
            }
    
            actionCompleted[actionHash] = true;
    
            users.push(_user);
            isUser[_user] = true;
    
            emit LogUserAdded(_user, signer);
        }
    
        function removeUser
        (
            address _user,
            uint _salt,
            bytes _signature
        ) 
            external
            note
            userExists(_user) 
            initialized
            onlyAdmin
        {   
            bytes32 actionHash = _getUserActionHash(_user, "REMOVE_USER", _salt);
    
            if(actionCompleted[actionHash]) {
                emit LogError("Account::removeUser", "ACTION_ALREADY_PERFORMED");
                return;
            }
    
            address signer = _recoverSigner(actionHash, _signature);
            
            if(users.length == 1){
                emit LogError("Account::removeUser",  "ACC_SHOULD_HAVE_ATLEAST_ONE_USER");
                return;
            }
            
            if(!isUser[signer]){
                emit LogError("Account::removeUser", "SIGNER_NOT_AUTHORIZED_WITH_ACCOUNT");
                return;
            }
            
            actionCompleted[actionHash] = true;
    
            // should delete value from isUser map? delete isUser[_user]?
            isUser[_user] = false;
            for (uint i = 0; i < users.length - 1; i++) {
                if (users[i] == _user) {
                    users[i] = users[users.length - 1];
                    users.length -= 1;
                    break;
                }
            }
    
            emit LogUserRemoved(_user, signer);
        }
    
        function _getTransferActionHash
        ( 
            address _token,
            address _to,
            uint _value,
            uint _salt
        ) 
            internal
            view
            returns (bytes32)
        {
            return keccak256(
                abi.encodePacked(
                    address(this),
                    _token,
                    _to,
                    _value,
                    _salt
                )
            );
        }
    
        function _getUserActionHash
        ( 
            address _user,
            string _action,
            uint _salt
        ) 
            internal
            view
            returns (bytes32)
        {
            return keccak256(
                abi.encodePacked(
                    address(this),
                    _user,
                    _action,
                    _salt
                )
            );
        }
    
        // to directly send ether to contract
        function() external payable {
            require(msg.data.length == 0 && msg.value > 0, "Account::fallback INVALID_ETHER_TRANSFER");
    
            if(msg.sender != address(weth9)){
                weth9.deposit.value(msg.value)();
            }
        }
    
        function changeImpl
        (
            address _to,
            uint _salt,
            bytes _signature
        )
            external 
            note 
            addressValid(_to)
            initialized
            onlyAdmin
        {   
            bytes32 actionHash = _getUserActionHash(_to, "CHANGE_ACCOUNT_IMPLEMENTATION", _salt);
            if(actionCompleted[actionHash])
            {
                emit LogError("Account::changeImpl", "ACTION_ALREADY_PERFORMED");
                return;
            }
    
            address signer = _recoverSigner(actionHash, _signature);
    
            if(!isUser[signer]) {
                emit LogError("Account::changeImpl", "SIGNER_NOT_AUTHORIZED_WITH_ACCOUNT");
                return;
            }
    
            actionCompleted[actionHash] = true;
    
            address oldImpl = masterCopy;
            this.changeMasterCopy(_to);
            
            emit LogImplChanged(_to, oldImpl);
        }
    
    }
    
    
    contract AccountFactory is DSStop, Utils {
        Config public config;
        mapping (address => bool) public isAccount;
        mapping (address => address[]) public userToAccounts;
        address[] public accounts;
    
        address public accountMaster;
    
        constructor
        (
            Config _config, 
            address _accountMaster
        ) 
        public 
        {
            config = _config;
            accountMaster = _accountMaster;
        }
    
        event LogAccountCreated(address indexed user, address indexed account, address by);
    
        modifier onlyAdmin() {
            require(config.isAdminValid(msg.sender), "AccountFactory::_ INVALID_ADMIN_ACCOUNT");
            _;
        }
    
        function setConfig(Config _config) external note auth addressValid(_config) {
            config = _config;
        }
    
        function setAccountMaster(address _accountMaster) external note auth addressValid(_accountMaster) {
            accountMaster = _accountMaster;
        }
    
        function newAccount(address _user)
            public
            note
            onlyAdmin
            addressValid(config)
            addressValid(accountMaster)
            whenNotStopped
            returns 
            (
                Account _account
            ) 
        {
            address proxy = new Proxy(accountMaster);
            _account = Account(proxy);
            _account.init(_user, config);
    
            accounts.push(_account);
            userToAccounts[_user].push(_account);
            isAccount[_account] = true;
    
            emit LogAccountCreated(_user, _account, msg.sender);
        }
        
        function batchNewAccount(address[] _users) public note onlyAdmin {
            for (uint i = 0; i < _users.length; i++) {
                newAccount(_users[i]);
            }
        }
    
        function getAllAccounts() public view returns (address[]) {
            return accounts;
        }
    
        function getAccountsForUser(address _user) public view returns (address[]) {
            return userToAccounts[_user];
        }
    
    }
    
    
    contract Escrow is DSNote, DSAuth {
    
        event LogTransfer(address indexed token, address indexed to, uint value);
        event LogTransferFromAccount(address indexed account, address indexed token, address indexed to, uint value);
    
        function transfer
        (
            address _token,
            address _to,
            uint _value
        )
            public
            note
            auth
        {
            require(ERC20(_token).transfer(_to, _value), "Escrow::transfer TOKEN_TRANSFER_FAILED");
            emit LogTransfer(_token, _to, _value);
        }
    
        function transferFromAccount
        (
            address _account,
            address _token,
            address _to,
            uint _value
        )
            public
            note
            auth
        {   
            Account(_account).transferBySystem(_token, _to, _value);
            emit LogTransferFromAccount(_account, _token, _to, _value);
        }
    
    }
    
    // issue with deploying multiple instances of same type in truffle, hence the following two contracts
    contract KernelEscrow is Escrow {
    
    }
    
    contract ReserveEscrow is Escrow {
        
    }
    
    
    contract Reserve is DSStop, DSThing, Utils, Utils2, ErrorUtils {
    
        Escrow public escrow;
        AccountFactory public accountFactory;
        DateTime public dateTime;
        Config public config;
        uint public deployTimestamp;
    
        string constant public VERSION = "1.0.0";
    
        uint public TIME_INTERVAL = 1 days;
        //uint public TIME_INTERVAL = 1 hours;
        
        constructor
        (
            Escrow _escrow,
            AccountFactory _accountFactory,
            DateTime _dateTime,
            Config _config
        ) 
        public 
        {
            escrow = _escrow;
            accountFactory = _accountFactory;
            dateTime = _dateTime;
            config = _config;
            deployTimestamp = now - (4 * TIME_INTERVAL);
        }
    
        function setEscrow(Escrow _escrow) 
            public 
            note 
            auth
            addressValid(_escrow)
        {
            escrow = _escrow;
        }
    
        function setAccountFactory(AccountFactory _accountFactory) 
            public 
            note 
            auth
            addressValid(_accountFactory)
        {
            accountFactory = _accountFactory;
        }
    
        function setDateTime(DateTime _dateTime) 
            public 
            note 
            auth
            addressValid(_dateTime)
        {
            dateTime = _dateTime;
        }
    
        function setConfig(Config _config) 
            public 
            note 
            auth
            addressValid(_config)
        {
            config = _config;
        }
    
        struct Order {
            address account;
            address token;
            address byUser;
            uint value;
            uint duration;
            uint expirationTimestamp;
            uint salt;
            uint createdTimestamp;
            bytes32 orderHash;
        }
    
        bytes32[] public orders;
        mapping (bytes32 => Order) public hashToOrder;
        mapping (bytes32 => bool) public isOrder;
        mapping (address => bytes32[]) public accountToOrders;
        mapping (bytes32 => bool) public cancelledOrders;
    
        // per day
        mapping (uint => mapping(address => uint)) public deposits;
        mapping (uint => mapping(address => uint)) public withdrawals;
        mapping (uint => mapping(address => uint)) public profits;
        mapping (uint => mapping(address => uint)) public losses;
    
        mapping (uint => mapping(address => uint)) public reserves;
        mapping (address => uint) public lastReserveRuns;
    
        mapping (address => mapping(address => uint)) public surplus;
    
        mapping (bytes32 => CumulativeRun) public orderToCumulative;
    
        struct CumulativeRun {
            uint timestamp;
            uint value;
        }
    
        modifier onlyAdmin() {
            require(config.isAdminValid(msg.sender), "Reserve::_ INVALID_ADMIN_ACCOUNT");
            _;
        }
    
        event LogOrderCreated(
            bytes32 indexed orderHash,
            address indexed account,
            address indexed token,
            address byUser,
            uint value,
            uint expirationTimestamp
        );
    
        event LogOrderCancelled(
            bytes32 indexed orderHash,
            address indexed by
        );
    
        event LogReserveValuesUpdated(
            address indexed token, 
            uint indexed updatedTill,
            uint reserve,
            uint profit,
            uint loss
        );
    
        event LogOrderCumulativeUpdated(
            bytes32 indexed orderHash,
            uint updatedTill,
            uint value
        );
    
        event LogRelease(
            address indexed token,
            address indexed to,
            uint value,
            address by
        );
    
        event LogLock(
            address indexed token,
            address indexed from,
            uint value,
            uint profit,
            uint loss,
            address by
        );
    
        event LogLockSurplus(
            address indexed forToken, 
            address indexed token,
            address from,
            uint value
        );
    
        event LogTransferSurplus(
            address indexed forToken,
            address indexed token,
            address to, 
            uint value
        );
        
        function createOrder
        (
            address[3] _orderAddresses,
            uint[3] _orderValues,
            bytes _signature
        ) 
            public
            note
            onlyAdmin
            whenNotStopped
        {
            Order memory order = _composeOrder(_orderAddresses, _orderValues);
            address signer = _recoverSigner(order.orderHash, _signature);
    
            if(signer != order.byUser){
                emit LogErrorWithHintBytes32(order.orderHash, "Reserve::createOrder", "SIGNER_NOT_ORDER_CREATOR");
                return;
            }
            
            if(isOrder[order.orderHash]){
                emit LogErrorWithHintBytes32(order.orderHash, "Reserve::createOrder", "ORDER_ALREADY_EXISTS");
                return;
            }
    
            if(!accountFactory.isAccount(order.account)){
                emit LogErrorWithHintBytes32(order.orderHash, "Reserve::createOrder", "INVALID_ORDER_ACCOUNT");
                return;
            }
    
            if(!Account(order.account).isUser(signer)){
                emit LogErrorWithHintBytes32(order.orderHash, "Reserve::createOrder", "SIGNER_NOT_AUTHORIZED_WITH_ACCOUNT");
                return;
            }
                    
            if(!_isOrderValid(order)) {
                emit LogErrorWithHintBytes32(order.orderHash, "Reserve::createOrder", "INVALID_ORDER_PARAMETERS");
                return;
            }
    
            if(ERC20(order.token).balanceOf(order.account) < order.value){
                emit LogErrorWithHintBytes32(order.orderHash, "Reserve::createOrder", "INSUFFICIENT_BALANCE_IN_ACCOUNT");
                return;
            }
    
            escrow.transferFromAccount(order.account, order.token, address(escrow), order.value);
            
            orders.push(order.orderHash);
            hashToOrder[order.orderHash] = order;
            isOrder[order.orderHash] = true;
            accountToOrders[order.account].push(order.orderHash);
    
            uint dateTimestamp = _getDateTimestamp(now);
    
            deposits[dateTimestamp][order.token] = add(deposits[dateTimestamp][order.token], order.value);
            
            orderToCumulative[order.orderHash].timestamp = _getDateTimestamp(order.createdTimestamp);
            orderToCumulative[order.orderHash].value = order.value;
    
            emit LogOrderCreated(
                order.orderHash,
                order.account,
                order.token,
                order.byUser,
                order.value,
                order.expirationTimestamp
            );
        }
    
        function cancelOrder
        (
            bytes32 _orderHash,
            bytes _signature
        )
            external
            note
            onlyAdmin
        {   
            if(!isOrder[_orderHash]) {
                emit LogErrorWithHintBytes32(_orderHash,"Reserve::createOrder", "ORDER_DOES_NOT_EXIST");
                return;
            }
    
            if(cancelledOrders[_orderHash]){
                emit LogErrorWithHintBytes32(_orderHash,"Reserve::createOrder", "ORDER_ALREADY_CANCELLED");
                return;
            }
    
            Order memory order = hashToOrder[_orderHash];
    
            bytes32 cancelOrderHash = _generateActionOrderHash(_orderHash, "CANCEL_RESERVE_ORDER");
            address signer = _recoverSigner(cancelOrderHash, _signature);
            
            if(!Account(order.account).isUser(signer)){
                emit LogErrorWithHintBytes32(_orderHash,"Reserve::createOrder", "SIGNER_NOT_AUTHORIZED_WITH_ACCOUNT");
                return;
            }
            
            doCancelOrder(order);
        }
        
        function processOrder
        (
            bytes32 _orderHash
        ) 
            external 
            note
            onlyAdmin
        {
            if(!isOrder[_orderHash]) {
                emit LogErrorWithHintBytes32(_orderHash,"Reserve::processOrder", "ORDER_DOES_NOT_EXIST");
                return;
            }
    
            if(cancelledOrders[_orderHash]){
                emit LogErrorWithHintBytes32(_orderHash,"Reserve::processOrder", "ORDER_ALREADY_CANCELLED");
                return;
            }
    
            Order memory order = hashToOrder[_orderHash];
    
            if(now > _getDateTimestamp(order.expirationTimestamp)) {
                doCancelOrder(order);
            } else {
                emit LogErrorWithHintBytes32(order.orderHash, "Reserve::processOrder", "ORDER_NOT_EXPIRED");
            }
        }
    
        function doCancelOrder(Order _order) 
            internal
        {   
            uint valueToTransfer = orderToCumulative[_order.orderHash].value;
    
            if(ERC20(_order.token).balanceOf(escrow) < valueToTransfer){
                emit LogErrorWithHintBytes32(_order.orderHash, "Reserve::doCancel", "INSUFFICIENT_BALANCE_IN_ESCROW");
                return;
            }
    
            uint nowDateTimestamp = _getDateTimestamp(now);
            cancelledOrders[_order.orderHash] = true;
            withdrawals[nowDateTimestamp][_order.token] = add(withdrawals[nowDateTimestamp][_order.token], valueToTransfer);
    
            escrow.transfer(_order.token, _order.account, valueToTransfer);
            emit LogOrderCancelled(_order.orderHash, msg.sender);
        }
    
        function release(address _token, address _to, uint _value) 
            external
            note
            auth
        {   
            require(ERC20(_token).balanceOf(escrow) >= _value, "Reserve::release INSUFFICIENT_BALANCE_IN_ESCROW");
            escrow.transfer(_token, _to, _value);
            emit LogRelease(_token, _to, _value, msg.sender);
        }
    
        // _value includes profit/loss as well
        function lock(address _token, address _from, uint _value, uint _profit, uint _loss)
            external
            note
            auth
        {   
            require(!(_profit == 0 && _loss == 0), "Reserve::lock INVALID_PROFIT_LOSS_VALUES");
            require(ERC20(_token).balanceOf(_from) >= _value, "Reserve::lock INSUFFICIENT_BALANCE");
                
            if(accountFactory.isAccount(_from)) {
                escrow.transferFromAccount(_from, _token, address(escrow), _value);
            } else {
                Escrow(_from).transfer(_token, address(escrow), _value);
            }
            
            uint dateTimestamp = _getDateTimestamp(now);
    
            if (_profit > 0){
                profits[dateTimestamp][_token] = add(profits[dateTimestamp][_token], _profit);
            } else if (_loss > 0) {
                losses[dateTimestamp][_token] = add(losses[dateTimestamp][_token], _loss);
            }
    
            emit LogLock(_token, _from, _value, _profit, _loss, msg.sender);
        }
    
        // to lock collateral if cannot be liquidated e.g. not enough reserves in kyber
        function lockSurplus(address _from, address _forToken, address _token, uint _value) 
            external
            note
            auth
        {
            require(ERC20(_token).balanceOf(_from) >= _value, "Reserve::lockSurplus INSUFFICIENT_BALANCE_IN_ESCROW");
    
            Escrow(_from).transfer(_token, address(escrow), _value);
            surplus[_forToken][_token] = add(surplus[_forToken][_token], _value);
    
            emit LogLockSurplus(_forToken, _token, _from, _value);
        }
    
        // to transfer surplus collateral out of the system to trade on other platforms and put back in terms of 
        // principal to reserve manually using an account or surplus escrow
        // should work in tandem with lock method when transferring back principal
        function transferSurplus(address _to, address _forToken, address _token, uint _value) 
            external
            note
            auth
        {
            require(ERC20(_token).balanceOf(escrow) >= _value, "Reserve::transferSurplus INSUFFICIENT_BALANCE_IN_ESCROW");
            require(surplus[_forToken][_token] >= _value, "Reserve::transferSurplus INSUFFICIENT_SURPLUS");
    
            surplus[_forToken][_token] = sub(surplus[_forToken][_token], _value);
            escrow.transfer(_token, _to, _value);
    
            emit LogTransferSurplus(_forToken, _token, _to, _value);
        }
    
        function updateReserveValues(address _token, uint _forDays)
            public
            note
            onlyAdmin
        {   
            uint lastReserveRun = lastReserveRuns[_token];
    
            if (lastReserveRun == 0) {
                lastReserveRun = _getDateTimestamp(deployTimestamp) - TIME_INTERVAL;
            }
    
            uint nowDateTimestamp = _getDateTimestamp(now);
            uint updatesLeft = ((nowDateTimestamp - TIME_INTERVAL) - lastReserveRun) / TIME_INTERVAL;
    
            if(updatesLeft == 0) {
                emit LogErrorWithHintAddress(_token, "Reserve::updateReserveValues", "RESERVE_VALUES_UP_TO_DATE");
                return;
            }
    
            uint counter = updatesLeft;
    
            if(updatesLeft > _forDays && _forDays > 0) {
                counter = _forDays;
            }
    
            for (uint i = 0; i < counter; i++) {
                reserves[lastReserveRun + TIME_INTERVAL][_token] = sub(
                    sub(
                        add(
                            add(
                                reserves[lastReserveRun][_token],
                                deposits[lastReserveRun + TIME_INTERVAL][_token]
                            ),
                            profits[lastReserveRun + TIME_INTERVAL][_token]
                        ),
                        losses[lastReserveRun + TIME_INTERVAL][_token]
                    ),
                    withdrawals[lastReserveRun + TIME_INTERVAL][_token]
                );
                lastReserveRuns[_token] = lastReserveRun + TIME_INTERVAL;
                lastReserveRun = lastReserveRuns[_token];
                
                emit LogReserveValuesUpdated(
                    _token,
                    lastReserveRun,
                    reserves[lastReserveRun][_token],
                    profits[lastReserveRun][_token],
                    losses[lastReserveRun][_token]
                );
                
            }
        }
    
        function updateOrderCumulativeValueBatch(bytes32[] _orderHashes, uint[] _forDays) 
            public
            note
            onlyAdmin
        {   
            if(_orderHashes.length != _forDays.length) {
                emit LogError("Reserve::updateOrderCumulativeValueBatch", "ARGS_ARRAYLENGTH_MISMATCH");
                return;
            }
    
            for(uint i = 0; i < _orderHashes.length; i++) {
                updateOrderCumulativeValue(_orderHashes[i], _forDays[i]);
            }
        }
    
        function updateOrderCumulativeValue
        (
            bytes32 _orderHash, 
            uint _forDays
        ) 
            public
            note
            onlyAdmin 
        {
            if(!isOrder[_orderHash]) {
                emit LogErrorWithHintBytes32(_orderHash, "Reserve::updateOrderCumulativeValue", "ORDER_DOES_NOT_EXIST");
                return;
            }
    
            if(cancelledOrders[_orderHash]) {
                emit LogErrorWithHintBytes32(_orderHash, "Reserve::updateOrderCumulativeValue", "ORDER_ALREADY_CANCELLED");
                return;
            }
            
            Order memory order = hashToOrder[_orderHash];
            CumulativeRun storage cumulativeRun = orderToCumulative[_orderHash];
            
            uint profitsAccrued = 0;
            uint lossesAccrued = 0;
            uint cumulativeValue = 0;
            uint counter = 0;
    
            uint lastOrderRun = cumulativeRun.timestamp;
            uint nowDateTimestamp = _getDateTimestamp(now);
    
            uint updatesLeft = ((nowDateTimestamp - TIME_INTERVAL) - lastOrderRun) / TIME_INTERVAL;
    
            if(updatesLeft == 0) {
                emit LogErrorWithHintBytes32(_orderHash, "Reserve::updateOrderCumulativeValue", "ORDER_VALUES_UP_TO_DATE");
                return;
            }
    
            counter = updatesLeft;
    
            if(updatesLeft > _forDays && _forDays > 0) {
                counter = _forDays;
            }
    
            for (uint i = 0; i < counter; i++){
                cumulativeValue = cumulativeRun.value;
                lastOrderRun = cumulativeRun.timestamp;
    
                if(lastReserveRuns[order.token] < lastOrderRun) {
                    emit LogErrorWithHintBytes32(_orderHash, "Reserve::updateOrderCumulativeValue", "RESERVE_VALUES_NOT_UPDATED");
                    emit LogOrderCumulativeUpdated(_orderHash, cumulativeRun.timestamp, cumulativeRun.value);
                    return;
                }
    
                profitsAccrued = div(
                    mul(profits[lastOrderRun + TIME_INTERVAL][order.token], cumulativeValue),
                    reserves[lastOrderRun][order.token]
                );
                    
                lossesAccrued = div(
                    mul(losses[lastOrderRun + TIME_INTERVAL][order.token], cumulativeValue),
                    reserves[lastOrderRun][order.token]
                );
    
                cumulativeValue = sub(add(cumulativeValue, profitsAccrued), lossesAccrued);
    
                cumulativeRun.timestamp = lastOrderRun + TIME_INTERVAL;
                cumulativeRun.value = cumulativeValue;
            }
            
            emit LogOrderCumulativeUpdated(_orderHash, cumulativeRun.timestamp, cumulativeRun.value);
        }
    
        function getAllOrders() 
            public
            view 
            returns 
            (
                bytes32[]
            ) 
        {
            return orders;
        }
    
        function getOrdersForAccount(address _account) 
            public
            view 
            returns 
            (
                bytes32[]
            )
        {
            return accountToOrders[_account];
        }
    
        function getOrder(bytes32 _orderHash)
            public 
            view 
            returns 
            (
                address _account,
                address _token,
                address _byUser,
                uint _value,
                uint _expirationTimestamp,
                uint _salt,
                uint _createdTimestamp
            )
        {   
            Order memory order = hashToOrder[_orderHash];
            return (
                order.account,
                order.token,
                order.byUser,
                order.value,
                order.expirationTimestamp,
                order.salt,
                order.createdTimestamp
            );
        }
    
        function _isOrderValid(Order _order)
            internal
            view
            returns (bool)
        {
            if(_order.account == address(0) || _order.byUser == address(0)
             || _order.value <= 0
             || _order.expirationTimestamp <= _order.createdTimestamp || _order.salt <= 0) {
                return false;
            }
    
            if(isOrder[_order.orderHash]) {
                return false;
            }
    
            if(cancelledOrders[_order.orderHash]) {
                return false;
            }
    
            return true;
        }
    
        function _composeOrder(address[3] _orderAddresses, uint[3] _orderValues)
            internal
            view
            returns (Order _order)
        {
            Order memory order = Order({
                account: _orderAddresses[0],
                token: _orderAddresses[1],
                byUser: _orderAddresses[2],
                value: _orderValues[0],
                createdTimestamp: now,
                duration: _orderValues[1],
                expirationTimestamp: add(now, _orderValues[1]),
                salt: _orderValues[2],
                orderHash: bytes32(0)
            });
    
            order.orderHash = _generateCreateOrderHash(order);
    
            return order;
        }
    
        function _generateCreateOrderHash(Order _order)
            internal
            pure //view
            returns (bytes32 _orderHash)
        {
            return keccak256(
                abi.encodePacked(
     //              address(this),
                    _order.account,
                    _order.token,
                    _order.value,
                    _order.duration,
                    _order.salt
                )
            );
        }
    
        function _generateActionOrderHash
        (
            bytes32 _orderHash,
            string _action
        )
            internal
            pure //view
            returns (bytes32 _repayOrderHash)
        {
            return keccak256(
                abi.encodePacked(
    //                address(this),
                    _orderHash,
                    _action
                )
            );
        }
    
        function _getDateTimestamp(uint _timestamp) 
            internal
            view
            returns (uint)
        {
            // 1 day
            return dateTime.toTimestamp(dateTime.getYear(_timestamp), dateTime.getMonth(_timestamp), dateTime.getDay(_timestamp));
            // 1 hour
            //return dateTime.toTimestamp(dateTime.getYear(_timestamp), dateTime.getMonth(_timestamp), dateTime.getDay(_timestamp), dateTime.getHour(_timestamp));
        } 
    
    }
    interface ExchangeConnector {
    
        function tradeWithInputFixed
        (   
            Escrow _escrow,
            address _srcToken,
            address _destToken,
            uint _srcTokenValue
        )
            external
            returns (uint _destTokenValue, uint _srcTokenValueLeft);
    
        function tradeWithOutputFixed
        (   
            Escrow _escrow,
            address _srcToken,
            address _destToken,
            uint _srcTokenValue,
            uint _maxDestTokenValue
        )
            external
            returns (uint _destTokenValue, uint _srcTokenValueLeft);
        
    
        function getExpectedRate(address _srcToken, address _destToken, uint _srcTokenValue) 
            external
            view
            returns(uint _expectedRate, uint _slippageRate);
        
        function isTradeFeasible(address _srcToken, address _destToken, uint _srcTokenValue) 
            external
            view
            returns(bool);
    
    }
    
    
    contract MKernel is DSStop, DSThing, Utils, Utils2, ErrorUtils {
        
        Escrow public escrow;
        AccountFactory public accountFactory;
        Reserve public reserve;
        address public feeWallet;
        Config public config;
        
        constructor
        (
            Escrow _escrow,
            AccountFactory _accountFactory,
            Reserve _reserve,
            address _feeWallet,
            Config _config
        ) 
        public 
        {
            escrow = _escrow;
            accountFactory = _accountFactory;
            reserve = _reserve;
            feeWallet = _feeWallet;
            config = _config;
        }
    
        function setEscrow(Escrow _escrow) 
            public 
            note 
            auth
            addressValid(_escrow)
        {
            escrow = _escrow;
        }
    
        function setAccountFactory(AccountFactory _accountFactory)
            public 
            note 
            auth
            addressValid(_accountFactory)
        {
            accountFactory = _accountFactory;
        }
    
        function setReserve(Reserve _reserve)
            public 
            note 
            auth
            addressValid(_reserve)
        {
            reserve = _reserve;
        }
    
        function setConfig(Config _config)
            public 
            note 
            auth
            addressValid(_config)
        {
            config = _config;
        }
    
        function setFeeWallet(address _feeWallet)
            public 
            note 
            auth
            addressValid(_feeWallet)
        {
            feeWallet = _feeWallet;
        }
        
        event LogOrderCreated(
            bytes32 indexed orderHash,
            uint tradeAmount,
            uint expirationTimestamp
        );
    
        event LogOrderLiquidatedByUser(
            bytes32 indexed orderHash
        );
    
        event LogOrderStoppedAtProfit(
            bytes32 indexed orderHash
        );
    
        event LogOrderDefaulted(
            bytes32 indexed orderHash,
            string reason
        );
    
        event LogNoActionPerformed(
            bytes32 indexed orderHash
        );
    
        event LogOrderSettlement(
            bytes32 indexed orderHash,
            uint valueRepaid,
            uint reserveProfit,
            uint reserveLoss,
            uint collateralLeft,
            uint userProfit,
            uint fee
        );
    
        struct Order {
            address account;
            address byUser;
            address principalToken; 
            address collateralToken;
            Trade trade;
            uint principalAmount;
            uint collateralAmount;
            uint premium;
            uint expirationTimestamp;
            uint duration;
            uint salt;
            uint fee;
            uint createdTimestamp;
            bytes32 orderHash;
        }
    
        struct Trade {
            address tradeToken;
            address closingToken;
            address exchangeConnector; //stores initial and then just used to pass around params
            uint stopProfit;
            uint stopLoss;
        }
    
        bytes32[] public orders;
        mapping (bytes32 => Order) public hashToOrder;
        mapping (bytes32 => bool) public isOrder;
        mapping (address => bytes32[]) public accountToOrders;
    
        mapping (bytes32 => uint) public initialTradeAmount;
        mapping (bytes32 => bool) public isLiquidated;
        mapping (bytes32 => bool) public isDefaulted;
    
        modifier onlyAdmin() {
            require(config.isAdminValid(msg.sender), "MKernel::_ INVALID_ADMIN_ACCOUNT");
            _;
        }
    
    
        function createOrder
        (
            address[6] _orderAddresses,
            uint[8] _orderValues,
            address _exchangeConnector,
            bytes _signature
        )    
            external
            note
            onlyAdmin
            whenNotStopped
            addressValid(_exchangeConnector)
        {
            Order memory order = _composeOrder(_orderAddresses, _orderValues);
            address signer = _recoverSigner(order.orderHash, _signature);
            order.trade.exchangeConnector = _exchangeConnector;
    
            if(signer != order.byUser) {
                emit LogErrorWithHintBytes32(order.orderHash, "MKernel::createOrder","SIGNER_NOT_ORDER_CREATOR");
                return;
            }
    
            if(isOrder[order.orderHash]){
                emit LogErrorWithHintBytes32(order.orderHash, "MKernel::createOrder","ORDER_ALREADY_EXISTS");
                return;
            }
    
            if(!accountFactory.isAccount(order.account)){
                emit LogErrorWithHintBytes32(order.orderHash, "MKernel::createOrder","INVALID_ORDER_ACCOUNT");
                return;
            }
    
            if(!Account(order.account).isUser(signer)) {
                emit LogErrorWithHintBytes32(order.orderHash, "MKernel::createOrder","SIGNER_NOT_AUTHORIZED_WITH_ACCOUNT");
                return;
            }
    
            if(!_isOrderValid(order)){
                emit LogErrorWithHintBytes32(order.orderHash, "MKernel::createOrder","INVALID_ORDER_PARAMETERS");
                return;
            }
    
            if(ERC20(order.collateralToken).balanceOf(order.account) < order.collateralAmount){
                emit LogErrorWithHintBytes32(order.orderHash, "MKernel::createOrder","INSUFFICIENT_COLLATERAL_IN_ACCOUNT");
                return;
            }
    
            if(ERC20(order.principalToken).balanceOf(reserve.escrow()) < order.principalAmount){
                emit LogErrorWithHintBytes32(order.orderHash, "MKernel::createOrder","INSUFFICIENT_FUNDS_IN_RESERVE");
                return;
            }
    
            if(!_isTradeFeasible(order, order.principalToken, order.trade.tradeToken, order.principalAmount))
            {
                emit LogErrorWithHintBytes32(order.orderHash, "MKernel::createOrder","TRADE_NOT_FEASIBLE");
                return;
            }        
    
            
            orders.push(order.orderHash);
            hashToOrder[order.orderHash] = order;
            isOrder[order.orderHash] = true;
            accountToOrders[order.account].push(order.orderHash);
    
            escrow.transferFromAccount(order.account, order.collateralToken, address(escrow), order.collateralAmount);
            reserve.release(order.principalToken, address(escrow), order.principalAmount);
        
            (initialTradeAmount[order.orderHash],) = _tradeWithFixedInput(
                order,
                ERC20(order.principalToken),
                ERC20(order.trade.tradeToken),
                order.principalAmount
            );
    
            emit LogOrderCreated(
                order.orderHash,
                initialTradeAmount[order.orderHash],
                order.expirationTimestamp
            );
            
    
        }
    
        function liquidateOrder
        (
            bytes32 _orderHash,
            address _exchangeConnector,
            bytes _signature
        ) 
            external
            note
            onlyAdmin
            addressValid(_exchangeConnector)
        {
            if(!isOrder[_orderHash]){
                emit LogErrorWithHintBytes32(_orderHash, "MKernel::liquidateOrder","ORDER_DOES_NOT_EXIST");
                return;
            }
            
            if(isLiquidated[_orderHash]){
                emit LogErrorWithHintBytes32(_orderHash, "MKernel::liquidateOrder","ORDER_ALREADY_LIQUIDATED");
                return;
            }
    
            if(isDefaulted[_orderHash]){
                emit LogErrorWithHintBytes32(_orderHash, "MKernel::liquidateOrder","ORDER_ALREADY_DEFAULTED");
                return;
            }
    
            bytes32 liquidateOrderHash = _generateLiquidateOrderHash(_orderHash);
            address signer = _recoverSigner(liquidateOrderHash, _signature);
    
            Order memory order = hashToOrder[_orderHash];
            order.trade.exchangeConnector = _exchangeConnector;
            
            if(!Account(order.account).isUser(signer)){
                emit LogErrorWithHintBytes32(_orderHash, "MKernel::liquidateOrder", "SIGNER_NOT_AUTHORIZED_WITH_ACCOUNT");
                return;
            }
    
            if(ERC20(order.trade.tradeToken).balanceOf(address(escrow)) < initialTradeAmount[_orderHash]){
                emit LogErrorWithHintBytes32(_orderHash, "MKernel::liquidateOrder", "INSUFFICIENT_TRADE_BALANCE_IN_ESCROW");
                return;
            }
    
            if(ERC20(order.collateralToken).balanceOf(address(escrow)) < order.collateralAmount){
                emit LogErrorWithHintBytes32(_orderHash, "MKernel::liquidateOrder", "INSUFFICIENT_COLLATERAL_BALANCE_IN_ESCROW");
                return;
            }
    
            isLiquidated[order.orderHash] = true;
            _performOrderLiquidation(order);
    
            emit LogOrderLiquidatedByUser(_orderHash);
        }
    
        function processTradeForExpiry
        (
            bytes32 _orderHash,
            address _exchangeConnector
        )
            external
            note
            onlyAdmin
            addressValid(_exchangeConnector)
        {
            if(!isOrder[_orderHash]){
                emit LogErrorWithHintBytes32(_orderHash, "MKernel::processTradeForExpiry","ORDER_DOES_NOT_EXIST");
                return;
            }
    
            if(isLiquidated[_orderHash]){
                emit LogErrorWithHintBytes32(_orderHash, "MKernel::processTradeForExpiry","ORDER_ALREADY_LIQUIDATED");
                return;
            }
    
            if(isDefaulted[_orderHash]){
                emit LogErrorWithHintBytes32(_orderHash, "MKernel::processTradeForExpiry","ORDER_ALREADY_DEFAULTED");
                return;
            }
            
    
            Order memory order = hashToOrder[_orderHash];
            order.trade.exchangeConnector = _exchangeConnector;
    
            if(ERC20(order.trade.tradeToken).balanceOf(address(escrow)) < initialTradeAmount[_orderHash]){
                emit LogErrorWithHintBytes32(_orderHash, "MKernel::processTradeForExpiry", "INSUFFICIENT_TRADE_BALANCE_IN_ESCROW");
                return;
            }
    
            if(ERC20(order.collateralToken).balanceOf(address(escrow)) < order.collateralAmount){
                emit LogErrorWithHintBytes32(_orderHash, "MKernel::processTradeForExpiry", "INSUFFICIENT_COLLATERAL_BALANCE_IN_ESCROW");
                return;
            }
    
            if(now > order.expirationTimestamp) {
                isDefaulted[order.orderHash] = true;
                _performOrderLiquidation(order);
                emit LogOrderDefaulted(order.orderHash, "MKERNEL_DUE_DATE_PASSED");
                return;
            }
    
            emit LogErrorWithHintBytes32(order.orderHash, "MKernel::processTradeForExpiry", "NO_ACTION_PERFORMED");
        }
    
    
        function processTradeForStopLoss
        (
            bytes32 _orderHash,
            address _exchangeConnector,
            uint[2] _tokenPrices,
            uint _bufferInPrincipal
        )
            external
            note
            onlyAdmin
            addressValid(_exchangeConnector)
        {   
            if(!isOrder[_orderHash]){
                emit LogErrorWithHintBytes32(_orderHash, "MKernel::processTradeForStopLoss","ORDER_DOES_NOT_EXIST");
                return;
            }
    
            if(isLiquidated[_orderHash]){
                emit LogErrorWithHintBytes32(_orderHash, "MKernel::processTradeForStopLoss","ORDER_ALREADY_LIQUIDATED");
                return;
            }
    
            if(isDefaulted[_orderHash]){
                emit LogErrorWithHintBytes32(_orderHash, "MKernel::processTradeForStopLoss","ORDER_ALREADY_DEFAULTED");
                return;
            }
    
            Order memory order = hashToOrder[_orderHash];
            order.trade.exchangeConnector = _exchangeConnector;
    
            if(ERC20(order.trade.tradeToken).balanceOf(address(escrow)) < initialTradeAmount[_orderHash]){
                emit LogErrorWithHintBytes32(_orderHash, "MKernel::processTradeForStopLoss", "INSUFFICIENT_TRADE_BALANCE_IN_ESCROW");
                return;
            }
    
            if(ERC20(order.collateralToken).balanceOf(address(escrow)) < order.collateralAmount){
                emit LogErrorWithHintBytes32(_orderHash, "MKernel::processTradeForStopLoss", "INSUFFICIENT_COLLATERAL_BALANCE_IN_ESCROW");
                return;
            }
    
            if(!_isPositionAboveStopLoss(order, _tokenPrices, _bufferInPrincipal)) {
                isDefaulted[order.orderHash] = true;
                _performOrderLiquidation(order);
                emit LogOrderDefaulted(order.orderHash, "MKERNEL_ORDER_UNSAFE");
                return;
            }
    
            emit LogErrorWithHintBytes32(order.orderHash, "MKernel::processTradeForStopLoss", "NO_ACTION_PERFORMED");
        }
    
        function processTradeForStopProfit
        (
            bytes32 _orderHash,
            address _exchangeConnector,
            uint[2] _tokenPrices,
            uint _bufferInPrincipal
        )
            external
            note
            onlyAdmin
            addressValid(_exchangeConnector)
        {   
            if(!isOrder[_orderHash]){
                emit LogErrorWithHintBytes32(_orderHash, "MKernel::processTradeForStopProfit","ORDER_DOES_NOT_EXIST");
                return;
            }
    
            if(isLiquidated[_orderHash]){
                emit LogErrorWithHintBytes32(_orderHash, "MKernel::processTradeForStopProfit","ORDER_ALREADY_LIQUIDATED");
                return;
            }
    
            if(isDefaulted[_orderHash]){
                emit LogErrorWithHintBytes32(_orderHash, "MKernel::processTradeForStopProfit","ORDER_ALREADY_DEFAULTED");
                return;
            }
    
            Order memory order = hashToOrder[_orderHash];
            order.trade.exchangeConnector = _exchangeConnector;
    
            if(ERC20(order.trade.tradeToken).balanceOf(address(escrow)) < initialTradeAmount[_orderHash]){
                emit LogErrorWithHintBytes32(_orderHash, "MKernel::processTradeForStopProfit", "INSUFFICIENT_TRADE_BALANCE_IN_ESCROW");
                return;
            }
    
            if(ERC20(order.collateralToken).balanceOf(address(escrow)) < order.collateralAmount){
                emit LogErrorWithHintBytes32(_orderHash, "MKernel::processTradeForStopProfit", "INSUFFICIENT_COLLATERAL_BALANCE_IN_ESCROW");
                return;
            }
            
            if(_isPositionAboveStopProfit(order, _tokenPrices, _bufferInPrincipal)) {
                isLiquidated[order.orderHash] = true;
                _performOrderLiquidation(order);
                emit LogOrderStoppedAtProfit(order.orderHash);
                return;
            }
    
            emit LogErrorWithHintBytes32(order.orderHash, "MKernel::processTradeForStopProfit", "NO_ACTION_PERFORMED");
        }
    
        function _performOrderLiquidation(Order _order) 
            internal
        {   
            uint tradeAmount = initialTradeAmount[_order.orderHash];
            uint valueToRepay = add(_order.principalAmount, _order.premium);
            uint valueToRepayWithFee = add(valueToRepay, _order.fee);
        
            uint principalFromTrade = 0;
            uint principalFromCollateral = 0;
            uint principalNeededFromCollateral = 0;
            uint collateralLeft = 0;
            uint userProfit = 0;
            uint totalPrincipalAcquired = 0;
            uint orderFee = 0;
            
    
            
                (principalFromTrade,) = _tradeWithFixedInput(_order, _order.trade.tradeToken, _order.principalToken, tradeAmount);
    
                if(principalFromTrade >= valueToRepayWithFee) {
                    userProfit = sub(principalFromTrade, valueToRepayWithFee);
                    orderFee = _order.fee;
                    _performSettlement(_order, valueToRepay, _order.premium, 0, _order.collateralAmount, userProfit, orderFee);
                } else {
    
                    principalNeededFromCollateral = sub(valueToRepayWithFee, principalFromTrade);
    
                    if (_order.collateralToken == _order.principalToken) {
                        principalFromCollateral = principalNeededFromCollateral;
    
                        if(_order.collateralAmount >= principalNeededFromCollateral) {
                            collateralLeft = sub(_order.collateralAmount, principalNeededFromCollateral);
                        }
    
                    } else {
                        (principalFromCollateral, collateralLeft) = _tradeWithFixedOutput(_order, _order.collateralToken, _order.principalToken, _order.collateralAmount, principalNeededFromCollateral);    
                    }
    
                    if(principalFromCollateral >= principalNeededFromCollateral) {
                        orderFee = _order.fee;
                        _performSettlement(_order, valueToRepay, _order.premium, 0, collateralLeft, 0, orderFee);
                    } else {
                        totalPrincipalAcquired = add(principalFromTrade, principalFromCollateral);
                        _performSettlementAfterAllPossibleLiquidations(_order, totalPrincipalAcquired);
                    }
                }
                           
        }
    
        function _tradeWithFixedInput(Order _order, address _srcToken, address _destToken, uint _srcTokenValue)
            internal
            returns (uint _destTokenValue, uint _srcTokenValueLeft)
        {
            ExchangeConnector exchangeConnector = ExchangeConnector(_order.trade.exchangeConnector);
            return exchangeConnector.tradeWithInputFixed(
                        escrow,
                        _srcToken,
                        _destToken,
                        _srcTokenValue
            );
        }
    
        function _tradeWithFixedOutput(Order _order, address _srcToken, address _destToken, uint _srcTokenValue, uint _maxDestTokenValue)
            internal
            returns (uint _destTokenValue, uint _srcTokenValueLeft)
        {
            ExchangeConnector exchangeConnector = ExchangeConnector(_order.trade.exchangeConnector);
            return exchangeConnector.tradeWithOutputFixed(
                        escrow,
                        _srcToken,
                        _destToken,
                        _srcTokenValue,
                        _maxDestTokenValue
            );
        }
    
        function _isTradeFeasible(Order _order, address _srcToken, address _destToken, uint _srcTokenValue)
            internal
            view
            returns (bool)
        {   
            ExchangeConnector exchangeConnector = ExchangeConnector(_order.trade.exchangeConnector);
            return exchangeConnector.isTradeFeasible(_srcToken, _destToken, _srcTokenValue);
        }
    
        function _performSettlementAfterAllPossibleLiquidations
        (
            Order _order,
            uint _totalPrincipalAcquired
        )
            internal
        {
            uint valueToRepay = add(_order.principalAmount, _order.premium);
    
            if(_totalPrincipalAcquired >= valueToRepay) {
                _performSettlement(_order, valueToRepay, _order.premium, 0, 0, 0, sub(_totalPrincipalAcquired, valueToRepay));
            } else if((_totalPrincipalAcquired < valueToRepay) && (_totalPrincipalAcquired >= _order.principalAmount)) {
                _performSettlement(_order, _totalPrincipalAcquired, sub(_totalPrincipalAcquired, _order.principalAmount), 0, 0, 0, 0);
            } else {
                _performSettlement(_order, _totalPrincipalAcquired, 0, sub(_order.principalAmount, _totalPrincipalAcquired), 0, 0, 0);
            }
    
        }
    
        function _performSettlement
        (
            Order _order,
            uint _valueRepaid,
            uint _reserveProfit,
            uint _reserveLoss,
            uint _collateralLeft,
            uint _userProfit,
            uint _fee
        ) 
            internal 
        {
            uint closingFromPrincipal = 0;
            uint userEarnings = _userProfit;
    
            if(_fee > 0){
                escrow.transfer(_order.principalToken, feeWallet, _fee);
            }
            
            reserve.lock(_order.principalToken, escrow, _valueRepaid, _reserveProfit, _reserveLoss);
            
            if(_collateralLeft > 0) {
                escrow.transfer(_order.collateralToken, _order.account, _collateralLeft);    
            }
    
            if(_userProfit > 0) {
                if(_order.trade.closingToken == _order.principalToken || !_isTradeFeasible(_order, _order.principalToken, _order.trade.closingToken, _userProfit)) {
                    escrow.transfer(_order.principalToken, _order.account, _userProfit);
                } else {
                    (closingFromPrincipal,) = _tradeWithFixedInput(_order, _order.principalToken, _order.trade.closingToken, _userProfit);
                    escrow.transfer(_order.trade.closingToken, _order.account, closingFromPrincipal);
                    userEarnings = closingFromPrincipal;
                }
            }
    
            emit LogOrderSettlement(_order.orderHash, _valueRepaid, _reserveProfit, _reserveLoss, _collateralLeft, userEarnings, _fee);
        }
    
        function _isPositionAboveStopLoss(Order _order, uint[2] _tokenPrices, uint _bufferInPrincipal)
            internal 
            view
            returns (bool)
        {
            uint principalPerCollateral = _tokenPrices[0]; 
            uint principalPerTrade = _tokenPrices[1];
            uint tradeAmount = initialTradeAmount[_order.orderHash];
    
            uint valueToRepayWithFee = add(add(_order.principalAmount, _order.premium), _order.fee);
            uint totalCollateralValueInPrincipal = div(mul(_order.collateralAmount, principalPerCollateral), WAD);
            uint totalTradeValueInPrincipal = div(mul(tradeAmount, principalPerTrade), WAD);
    
            uint bufferValue = div(mul(_order.principalAmount, _bufferInPrincipal), WAD);
            uint minValueReq = div(mul(_order.trade.stopLoss, totalCollateralValueInPrincipal), WAD);
    
            if(add(valueToRepayWithFee, bufferValue) >= totalTradeValueInPrincipal && 
                sub(add(valueToRepayWithFee, bufferValue), totalTradeValueInPrincipal) >= minValueReq) 
            {
                return false;
            }
    
            return true;
        }
    
        function _isPositionAboveStopProfit(Order _order, uint[2] _tokenPrices, uint _bufferInPrincipal)
            internal 
            view
            returns (bool)
        {       
            if(_order.trade.stopProfit == 0) {
                return false;
            } else {
                uint principalPerTrade = _tokenPrices[1];
                uint tradeAmount = initialTradeAmount[_order.orderHash];
    
                uint valueToRepayWithFee = add(add(_order.principalAmount, _order.premium), _order.fee);
                uint totalTradeValueInPrincipal = div(mul(tradeAmount, principalPerTrade), WAD);
    
                uint stopProfitValue = div(mul(_order.principalAmount, _order.trade.stopProfit), WAD);
                uint bufferValue = div(mul(_order.principalAmount, _bufferInPrincipal), WAD);
    
                if(totalTradeValueInPrincipal >= add(add(valueToRepayWithFee, stopProfitValue), bufferValue)) {
                    return true;
                }
    
                return false;
            }
        }
    
        function _generateLiquidateOrderHash
        (
            bytes32 _orderHash
        )
            internal
            view
            returns (bytes32 _liquidateOrderHash)
        {
            return keccak256(
                abi.encodePacked(
                    address(this),
                    _orderHash,
                    "CANCEL_MKERNEL_ORDER"
                )
            );
        }
    
        function _isOrderValid(Order _order)
            internal
            pure
            returns (bool)
        {
            if(_order.account == address(0) || _order.byUser == address(0)
             || _order.principalToken == address(0) || _order.collateralToken == address(0)
             || _order.trade.closingToken == address(0)
             || _order.trade.tradeToken == address(0)
             || (_order.trade.tradeToken == _order.principalToken) || _order.trade.exchangeConnector == address(0)
             || _order.principalAmount == 0 || _order.collateralAmount == 0
             || _order.premium == 0
             || _order.expirationTimestamp <= _order.createdTimestamp || _order.salt == 0) {
                return false;
            }
    
            return true;
        }
    
        function _composeOrder
        (
            address[6] _orderAddresses,
            uint[8] _orderValues
        )
            internal
            view
            returns (Order _order)
        {   
            Trade memory trade = _composeTrade(_orderAddresses[4], _orderAddresses[5], _orderValues[6], _orderValues[7]);
    
            Order memory order = Order({
                account: _orderAddresses[0],
                byUser: _orderAddresses[1],
                principalToken: _orderAddresses[2],
                collateralToken: _orderAddresses[3],
                principalAmount: _orderValues[0],
                collateralAmount: _orderValues[1],
                premium: _orderValues[2],
                duration: _orderValues[3],
                expirationTimestamp: add(now, _orderValues[3]),
                salt: _orderValues[4],
                fee: _orderValues[5],
                createdTimestamp: now,
                orderHash: bytes32(0),
                trade: trade
            });
    
            order.orderHash = _generateOrderHash(order);
        
            return order;
        }
    
        function _composeTrade
        (
            address _tradeToken,
            address _closingToken,
            uint _stopProfit,
            uint _stopLoss
        )
            internal 
            pure
            returns (Trade _trade)
        {
            _trade = Trade({
                tradeToken: _tradeToken,
                closingToken: _closingToken,
                stopProfit: _stopProfit,
                stopLoss: _stopLoss,
                exchangeConnector: address(0)
            });
        }
    
        function _generateOrderHash(Order _order)
            internal
            view
            returns (bytes32 _orderHash)
        {
            return keccak256(
                abi.encodePacked(
                    address(this),
                    _generateOrderHash1(_order),
                    _generateOrderHash2(_order)
                )
            );
        }
    
        function _generateOrderHash1(Order _order)
            internal
            view
            returns (bytes32 _orderHash1) 
        {
            return keccak256(
                abi.encodePacked(
                    address(this),
                    _order.account,
                    _order.principalToken,
                    _order.collateralToken,
                    _order.principalAmount,
                    _order.collateralAmount,
                    _order.premium,
                    _order.duration,
                    _order.salt,
                    _order.fee
                )
            );
        }
    
        function _generateOrderHash2(Order _order)
            internal
            view
            returns (bytes32 _orderHash2)
        {
            return keccak256(
                abi.encodePacked(
                    address(this),
                    _order.trade.tradeToken,
                    _order.trade.closingToken,
                    _order.trade.stopProfit,
                    _order.trade.stopLoss,
                    _order.salt
                )
            );
        }
    
        function getAllOrders()
            public 
            view 
            returns 
            (
                bytes32[]
            )
        {
            return orders;
        }
    
        
        function getOrder(bytes32 _orderHash)
            public 
            view 
            returns 
            (
                address _account,
                address _byUser,
                address _principalToken,
                address _collateralToken,
                uint _principalAmount,
                uint _collateralAmount,
                uint _premium,
                uint _expirationTimestamp,
                uint _salt,
                uint _fee,
                uint _createdTimestamp
            )
        {   
            Order memory order = hashToOrder[_orderHash];
            return (
                order.account,
                order.byUser,
                order.principalToken,
                order.collateralToken,
                order.principalAmount,
                order.collateralAmount,
                order.premium,
                order.expirationTimestamp,
                order.salt,
                order.fee,
                order.createdTimestamp
            );
        }
    
        function getTrade(bytes32 _orderHash)
            public 
            view 
            returns 
            (
                address _tradeToken,
                address _closingToken,
                address _initExchangeConnector,
                uint _stopProfit,
                uint _stopLoss
            )
        {   
            Order memory order = hashToOrder[_orderHash];
            return (
                order.trade.tradeToken,
                order.trade.closingToken,
                order.trade.exchangeConnector,
                order.trade.stopProfit,
                order.trade.stopLoss
            );
        }
    
        function getOrdersForAccount(address _account) 
            public
            view 
            returns 
            (
                bytes32[]
            )
        {
            return accountToOrders[_account];
        }
    
    }

    File 2 of 3: Config
    pragma solidity 0.4.24;
    
    
    contract DSNote {
        event LogNote(
            bytes4   indexed  sig,
            address  indexed  guy,
            bytes32  indexed  foo,
            bytes32  indexed  bar,
            uint              wad,
            bytes             fax
        ) anonymous;
    
        modifier note {
            bytes32 foo;
            bytes32 bar;
    
            assembly {
                foo := calldataload(4)
                bar := calldataload(36)
            }
    
            emit LogNote(msg.sig, msg.sender, foo, bar, msg.value, msg.data);
    
            _;
        }
    }
    
    
    contract DSAuthority {
        function canCall(address src, address dst, bytes4 sig) public view returns (bool);
    }
    
    
    contract DSAuthEvents {
        event LogSetAuthority (address indexed authority);
        event LogSetOwner     (address indexed owner);
    }
    
    
    contract DSAuth is DSAuthEvents {
        DSAuthority  public  authority;
        address      public  owner;
    
        constructor() public {
            owner = msg.sender;
            emit LogSetOwner(msg.sender);
        }
    
        function setOwner(address owner_)
            public
            auth
        {
            owner = owner_;
            emit LogSetOwner(owner);
        }
    
        function setAuthority(DSAuthority authority_)
            public
            auth
        {
            authority = authority_;
            emit LogSetAuthority(authority);
        }
    
        modifier auth {
            require(isAuthorized(msg.sender, msg.sig), "DSAuth::_ SENDER_NOT_AUTHORIZED");
            _;
        }
    
        function isAuthorized(address src, bytes4 sig) internal view returns (bool) {
            if (src == address(this)) {
                return true;
            } else if (src == owner) {
                return true;
            } else if (authority == DSAuthority(0)) {
                return false;
            } else {
                return authority.canCall(src, this, sig);
            }
        }
    }
    
    
    contract WETH9 {
        string public name     = "Wrapped Ether";
        string public symbol   = "WETH";
        uint8  public decimals = 18;
    
        event  Approval(address indexed _owner, address indexed _spender, uint _value);
        event  Transfer(address indexed _from, address indexed _to, uint _value);
        event  Deposit(address indexed _owner, uint _value);
        event  Withdrawal(address indexed _owner, uint _value);
    
        mapping (address => uint)                       public  balanceOf;
        mapping (address => mapping (address => uint))  public  allowance;
    
        function() public payable {
            deposit();
        }
    
        function deposit() public payable {
            balanceOf[msg.sender] += msg.value;
            Deposit(msg.sender, msg.value);
        }
    
        function withdraw(uint wad) public {
            require(balanceOf[msg.sender] >= wad);
            balanceOf[msg.sender] -= wad;
            msg.sender.transfer(wad);
            Withdrawal(msg.sender, wad);
        }
    
        function totalSupply() public view returns (uint) {
            return this.balance;
        }
    
        function approve(address guy, uint wad) public returns (bool) {
            allowance[msg.sender][guy] = wad;
            Approval(msg.sender, guy, wad);
            return true;
        }
    
        function transfer(address dst, uint wad) public returns (bool) {
            return transferFrom(msg.sender, dst, wad);
        }
    
        function transferFrom(address src, address dst, uint wad)
            public
            returns (bool)
        {
            require(balanceOf[src] >= wad);
    
            if (src != msg.sender && allowance[src][msg.sender] != uint(-1)) {
                require(allowance[src][msg.sender] >= wad);
                allowance[src][msg.sender] -= wad;
            }
    
            balanceOf[src] -= wad;
            balanceOf[dst] += wad;
    
            Transfer(src, dst, wad);
    
            return true;
        }
    }
    
    
    contract Utils {
    
        modifier addressValid(address _address) {
            require(_address != address(0), "Utils::_ INVALID_ADDRESS");
            _;
        }
    
    }
    
    
    contract Config is DSNote, DSAuth, Utils {
    
        WETH9 public weth9;
        mapping (address => bool) public isAccountHandler;
        mapping (address => bool) public isAdmin;
        address[] public admins;
        bool public disableAdminControl = false;
        
        event LogAdminAdded(address indexed _admin, address _by);
        event LogAdminRemoved(address indexed _admin, address _by);
    
        constructor() public {
            admins.push(msg.sender);
            isAdmin[msg.sender] = true;
        }
    
        modifier onlyAdmin(){
            require(isAdmin[msg.sender], "Config::_ SENDER_NOT_AUTHORIZED");
            _;
        }
    
        function setWETH9
        (
            address _weth9
        ) 
            public
            auth
            note
            addressValid(_weth9) 
        {
            weth9 = WETH9(_weth9);
        }
    
        function setAccountHandler
        (
            address _accountHandler,
            bool _isAccountHandler
        )
            public
            auth
            note
            addressValid(_accountHandler)
        {
            isAccountHandler[_accountHandler] = _isAccountHandler;
        }
    
        function toggleAdminsControl() 
            public
            auth
            note
        {
            disableAdminControl = !disableAdminControl;
        }
    
        function isAdminValid(address _admin)
            public
            view
            returns (bool)
        {
            if(disableAdminControl) {
                return true;
            } else {
                return isAdmin[_admin];
            }
        }
    
        function getAllAdmins()
            public
            view
            returns(address[])
        {
            return admins;
        }
    
        function addAdmin
        (
            address _admin
        )
            external
            note
            onlyAdmin
            addressValid(_admin)
        {   
            require(!isAdmin[_admin], "Config::addAdmin ADMIN_ALREADY_EXISTS");
    
            admins.push(_admin);
            isAdmin[_admin] = true;
    
            emit LogAdminAdded(_admin, msg.sender);
        }
    
        function removeAdmin
        (
            address _admin
        ) 
            external
            note
            onlyAdmin
            addressValid(_admin)
        {   
            require(isAdmin[_admin], "Config::removeAdmin ADMIN_DOES_NOT_EXIST");
            require(msg.sender != _admin, "Config::removeAdmin ADMIN_NOT_AUTHORIZED");
    
            isAdmin[_admin] = false;
    
            for (uint i = 0; i < admins.length - 1; i++) {
                if (admins[i] == _admin) {
                    admins[i] = admins[admins.length - 1];
                    admins.length -= 1;
                    break;
                }
            }
    
            emit LogAdminRemoved(_admin, msg.sender);
        }
    }

    File 3 of 3: LinkToken
    pragma solidity ^0.4.16;
    
    
    /**
     * @title SafeMath
     * @dev Math operations with safety checks that throw on error
     */
    library SafeMath {
      function mul(uint256 a, uint256 b) internal constant returns (uint256) {
        uint256 c = a * b;
        assert(a == 0 || c / a == b);
        return c;
      }
    
      function div(uint256 a, uint256 b) internal constant returns (uint256) {
        // assert(b > 0); // Solidity automatically throws when dividing by 0
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold
        return c;
      }
    
      function sub(uint256 a, uint256 b) internal constant returns (uint256) {
        assert(b <= a);
        return a - b;
      }
    
      function add(uint256 a, uint256 b) internal constant returns (uint256) {
        uint256 c = a + b;
        assert(c >= a);
        return c;
      }
    }
    
    
    /**
     * @title ERC20Basic
     * @dev Simpler version of ERC20 interface
     * @dev see https://github.com/ethereum/EIPs/issues/179
     */
    contract ERC20Basic {
      uint256 public totalSupply;
      function balanceOf(address who) constant returns (uint256);
      function transfer(address to, uint256 value) returns (bool);
      event Transfer(address indexed from, address indexed to, uint256 value);
    }
    /**
     * @title ERC20 interface
     * @dev see https://github.com/ethereum/EIPs/issues/20
     */
    contract ERC20 is ERC20Basic {
      function allowance(address owner, address spender) constant returns (uint256);
      function transferFrom(address from, address to, uint256 value) returns (bool);
      function approve(address spender, uint256 value) returns (bool);
      event Approval(address indexed owner, address indexed spender, uint256 value);
    }
    
    contract ERC677 is ERC20 {
      function transferAndCall(address to, uint value, bytes data) returns (bool success);
    
      event Transfer(address indexed from, address indexed to, uint value, bytes data);
    }
    
    contract ERC677Receiver {
      function onTokenTransfer(address _sender, uint _value, bytes _data);
    }
    
    /**
     * @title Basic token
     * @dev Basic version of StandardToken, with no allowances. 
     */
    contract BasicToken is ERC20Basic {
      using SafeMath for uint256;
    
      mapping(address => uint256) balances;
    
      /**
      * @dev transfer token for a specified address
      * @param _to The address to transfer to.
      * @param _value The amount to be transferred.
      */
      function transfer(address _to, uint256 _value) returns (bool) {
        balances[msg.sender] = balances[msg.sender].sub(_value);
        balances[_to] = balances[_to].add(_value);
        Transfer(msg.sender, _to, _value);
        return true;
      }
    
      /**
      * @dev Gets the balance of the specified address.
      * @param _owner The address to query the the balance of. 
      * @return An uint256 representing the amount owned by the passed address.
      */
      function balanceOf(address _owner) constant returns (uint256 balance) {
        return balances[_owner];
      }
    
    }
    
    
    /**
     * @title Standard ERC20 token
     *
     * @dev Implementation of the basic standard token.
     * @dev https://github.com/ethereum/EIPs/issues/20
     * @dev Based on code by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
     */
    contract StandardToken is ERC20, BasicToken {
    
      mapping (address => mapping (address => uint256)) allowed;
    
    
      /**
       * @dev Transfer tokens from one address to another
       * @param _from address The address which you want to send tokens from
       * @param _to address The address which you want to transfer to
       * @param _value uint256 the amount of tokens to be transferred
       */
      function transferFrom(address _from, address _to, uint256 _value) returns (bool) {
        var _allowance = allowed[_from][msg.sender];
    
        // Check is not needed because sub(_allowance, _value) will already throw if this condition is not met
        // require (_value <= _allowance);
    
        balances[_from] = balances[_from].sub(_value);
        balances[_to] = balances[_to].add(_value);
        allowed[_from][msg.sender] = _allowance.sub(_value);
        Transfer(_from, _to, _value);
        return true;
      }
    
      /**
       * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
       * @param _spender The address which will spend the funds.
       * @param _value The amount of tokens to be spent.
       */
      function approve(address _spender, uint256 _value) returns (bool) {
        allowed[msg.sender][_spender] = _value;
        Approval(msg.sender, _spender, _value);
        return true;
      }
    
      /**
       * @dev Function to check the amount of tokens that an owner allowed to a spender.
       * @param _owner address The address which owns the funds.
       * @param _spender address The address which will spend the funds.
       * @return A uint256 specifying the amount of tokens still available for the spender.
       */
      function allowance(address _owner, address _spender) constant returns (uint256 remaining) {
        return allowed[_owner][_spender];
      }
      
        /*
       * approve should be called when allowed[_spender] == 0. To increment
       * allowed value is better to use this function to avoid 2 calls (and wait until 
       * the first transaction is mined)
       * From MonolithDAO Token.sol
       */
      function increaseApproval (address _spender, uint _addedValue) 
        returns (bool success) {
        allowed[msg.sender][_spender] = allowed[msg.sender][_spender].add(_addedValue);
        Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
        return true;
      }
    
      function decreaseApproval (address _spender, uint _subtractedValue) 
        returns (bool success) {
        uint oldValue = allowed[msg.sender][_spender];
        if (_subtractedValue > oldValue) {
          allowed[msg.sender][_spender] = 0;
        } else {
          allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue);
        }
        Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
        return true;
      }
    
    }
    
    contract ERC677Token is ERC677 {
    
      /**
      * @dev transfer token to a contract address with additional data if the recipient is a contact.
      * @param _to The address to transfer to.
      * @param _value The amount to be transferred.
      * @param _data The extra data to be passed to the receiving contract.
      */
      function transferAndCall(address _to, uint _value, bytes _data)
        public
        returns (bool success)
      {
        super.transfer(_to, _value);
        Transfer(msg.sender, _to, _value, _data);
        if (isContract(_to)) {
          contractFallback(_to, _value, _data);
        }
        return true;
      }
    
    
      // PRIVATE
    
      function contractFallback(address _to, uint _value, bytes _data)
        private
      {
        ERC677Receiver receiver = ERC677Receiver(_to);
        receiver.onTokenTransfer(msg.sender, _value, _data);
      }
    
      function isContract(address _addr)
        private
        returns (bool hasCode)
      {
        uint length;
        assembly { length := extcodesize(_addr) }
        return length > 0;
      }
    
    }
    
    contract LinkToken is StandardToken, ERC677Token {
    
      uint public constant totalSupply = 10**27;
      string public constant name = 'ChainLink Token';
      uint8 public constant decimals = 18;
      string public constant symbol = 'LINK';
    
      function LinkToken()
        public
      {
        balances[msg.sender] = totalSupply;
      }
    
      /**
      * @dev transfer token to a specified address with additional data if the recipient is a contract.
      * @param _to The address to transfer to.
      * @param _value The amount to be transferred.
      * @param _data The extra data to be passed to the receiving contract.
      */
      function transferAndCall(address _to, uint _value, bytes _data)
        public
        validRecipient(_to)
        returns (bool success)
      {
        return super.transferAndCall(_to, _value, _data);
      }
    
      /**
      * @dev transfer token to a specified address.
      * @param _to The address to transfer to.
      * @param _value The amount to be transferred.
      */
      function transfer(address _to, uint _value)
        public
        validRecipient(_to)
        returns (bool success)
      {
        return super.transfer(_to, _value);
      }
    
      /**
       * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
       * @param _spender The address which will spend the funds.
       * @param _value The amount of tokens to be spent.
       */
      function approve(address _spender, uint256 _value)
        public
        validRecipient(_spender)
        returns (bool)
      {
        return super.approve(_spender,  _value);
      }
    
      /**
       * @dev Transfer tokens from one address to another
       * @param _from address The address which you want to send tokens from
       * @param _to address The address which you want to transfer to
       * @param _value uint256 the amount of tokens to be transferred
       */
      function transferFrom(address _from, address _to, uint256 _value)
        public
        validRecipient(_to)
        returns (bool)
      {
        return super.transferFrom(_from, _to, _value);
      }
    
    
      // MODIFIERS
    
      modifier validRecipient(address _recipient) {
        require(_recipient != address(0) && _recipient != address(this));
        _;
      }
    
    }