ETH Price: $2,412.48 (+6.39%)
Gas: 2.25 Gwei

Transaction Decoder

Block:
7443228 at Mar-26-2019 08:36:38 AM +UTC
Transaction Fee:
0.000883762 ETH $2.13
Gas Used:
80,342 Gas / 11 Gwei

Account State Difference:

  Address   Before After State Difference Code
0x04F06280...c989C2342
(Joyso)
1,119.101580232988892124 Eth1,109.07475876543862902 Eth10.026821467550263104
0x535C21De...9c5693da3 0.296595625299234777 Eth10.323417092849497881 Eth10.026821467550263104
(Spark Pool)
6,768.710415981991706921 Eth6,768.711299743991706921 Eth0.000883762
0x828d15b4...4Abf90d08
3.334494658717643363 Eth
Nonce: 32509
3.333610896717643363 Eth
Nonce: 32510
0.000883762

Execution Trace

Joyso.withdrawByAdmin_Unau( inputs=[10026821467550263104, 13000981, 41884687097051095516706453223716470425945304635348125385929595082261661745155, 11589777113402799024871432384128951494111440479215082705623623074799578067322, 10965690267588402189549122954269424585427579603415690509863931005529159931520] )
  • Null: 0x000...001.218aaec8( )
  • ETH 10.026821467550263104 0x535c21de50fa2c1810c7cd0438bf8589c5693da3.CALL( )
    pragma solidity 0.4.19;
    
    /**
     * @title Ownable
     * @dev The Ownable contract has an owner address, and provides basic authorization control
     * functions, this simplifies the implementation of "user permissions".
     */
    contract Ownable {
      address public owner;
    
    
      event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    
    
      /**
       * @dev The Ownable constructor sets the original `owner` of the contract to the sender
       * account.
       */
      function Ownable() public {
        owner = msg.sender;
      }
    
      /**
       * @dev Throws if called by any account other than the owner.
       */
      modifier onlyOwner() {
        require(msg.sender == owner);
        _;
      }
    
      /**
       * @dev Allows the current owner to transfer control of the contract to a newOwner.
       * @param newOwner The address to transfer ownership to.
       */
      function transferOwnership(address newOwner) public onlyOwner {
        require(newOwner != address(0));
        OwnershipTransferred(owner, newOwner);
        owner = newOwner;
      }
    
    }
    
    /**
     * @title ERC20Basic
     * @dev Simpler version of ERC20 interface
     * @dev see https://github.com/ethereum/EIPs/issues/179
     */
    contract ERC20Basic {
      function totalSupply() public view returns (uint256);
      function balanceOf(address who) public view returns (uint256);
      function transfer(address to, uint256 value) public 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) public view returns (uint256);
      function transferFrom(address from, address to, uint256 value) public returns (bool);
      function approve(address spender, uint256 value) public returns (bool);
      event Approval(address indexed owner, address indexed spender, uint256 value);
    }
    
    /**
     * @title SafeMath
     * @dev Math operations with safety checks that throw on error
     */
    library SafeMath {
        function mul(uint256 a, uint256 b) internal pure returns (uint256) {
            if (a == 0) {
                return 0;
            }
            uint256 c = a * b;
            require(c / a == b);
            return c;
        }
    
        function sub(uint256 a, uint256 b) internal pure returns (uint256) {
            require(b <= a);
            return a - b;
        }
    
        function add(uint256 a, uint256 b) internal pure returns (uint256) {
            uint256 c = a + b;
            require(c >= a);
            return c;
        }
    }
    
    /**
     * @title Migratable
     * @dev an interface for joyso to migrate to the new version
     */
    contract Migratable {
        function migrate(address user, uint256 amount, address tokenAddr) external payable returns (bool);
    }
    
    /**
     * @title JoysoDataDecoder
     * @author Will, Emn178
     * @notice decode the joyso compressed data
     */
    contract JoysoDataDecoder {
        function decodeOrderUserId(uint256 data) internal pure returns (uint256) {
            return data & 0x00000000000000000000000000000000000000000000000000000000ffffffff;
        }
    
        function retrieveV(uint256 data) internal pure returns (uint256) {
            // [24..24] v 0:27 1:28
            return data & 0x000000000000000000000000f000000000000000000000000000000000000000 == 0 ? 27 : 28;
        }
    }
    
    
    /**
     * @title Joyso
     * @notice joyso main contract
     * @author Will, Emn178
     */
    contract Joyso is Ownable, JoysoDataDecoder {
        using SafeMath for uint256;
    
        uint256 private constant USER_MASK = 0x00000000000000000000000000000000000000000000000000000000ffffffff;
        uint256 private constant PAYMENT_METHOD_MASK = 0x00000000000000000000000f0000000000000000000000000000000000000000;
        uint256 private constant WITHDRAW_TOKEN_MASK = 0x0000000000000000000000000000000000000000000000000000ffff00000000;
        uint256 private constant V_MASK = 0x000000000000000000000000f000000000000000000000000000000000000000;
        uint256 private constant TOKEN_SELL_MASK = 0x000000000000000000000000000000000000000000000000ffff000000000000;
        uint256 private constant TOKEN_BUY_MASK = 0x0000000000000000000000000000000000000000000000000000ffff00000000;
        uint256 private constant SIGN_MASK = 0xffffffffffffffffffffffff0000000000000000000000000000000000000000;
        uint256 private constant MATCH_SIGN_MASK = 0xfffffffffffffffffffffff00000000000000000000000000000000000000000;
        uint256 private constant TOKEN_JOY_PRICE_MASK = 0x0000000000000000000000000fffffffffffffffffffffff0000000000000000;
        uint256 private constant JOY_PRICE_MASK = 0x0000000000000000fffffff00000000000000000000000000000000000000000;
        uint256 private constant IS_BUY_MASK = 0x00000000000000000000000f0000000000000000000000000000000000000000;
        uint256 private constant TAKER_FEE_MASK = 0x00000000ffff0000000000000000000000000000000000000000000000000000;
        uint256 private constant MAKER_FEE_MASK = 0x000000000000ffff000000000000000000000000000000000000000000000000;
    
        uint256 private constant PAY_BY_TOKEN = 0x0000000000000000000000020000000000000000000000000000000000000000;
        uint256 private constant PAY_BY_JOY = 0x0000000000000000000000010000000000000000000000000000000000000000;
        uint256 private constant ORDER_ISBUY = 0x0000000000000000000000010000000000000000000000000000000000000000;
    
        mapping (address => mapping (address => uint256)) private balances;
        mapping (address => uint256) public userLock;
        mapping (address => uint256) public userNonce;
        mapping (bytes32 => uint256) public orderFills;
        mapping (bytes32 => bool) public usedHash;
        mapping (address => bool) public isAdmin;
        mapping (uint256 => address) public tokenId2Address;
        mapping (uint256 => address) public userId2Address;
        mapping (address => uint256) public userAddress2Id;
        mapping (address => uint256) public tokenAddress2Id;
    
        address public joysoWallet;
        address public joyToken;
        uint256 public lockPeriod = 30 days;
        uint256 public userCount;
        bool public tradeEventEnabled = true;
    
        modifier onlyAdmin {
            require(msg.sender == owner || isAdmin[msg.sender]);
            _;
        }
    
        //events
        event Deposit(address token, address user, uint256 amount, uint256 balance);
        event Withdraw(address token, address user, uint256 amount, uint256 balance);
        event NewUser(address user, uint256 id);
        event Lock(address user, uint256 timeLock);
    
        // for debug
        event TradeSuccess(address user, uint256 baseAmount, uint256 tokenAmount, bool isBuy, uint256 fee);
    
        function Joyso(address _joysoWallet, address _joyToken) public {
            joysoWallet = _joysoWallet;
            addUser(_joysoWallet);
            joyToken = _joyToken;
            tokenAddress2Id[joyToken] = 1;
            tokenAddress2Id[0] = 0; // ether address is Id 0
            tokenId2Address[0] = 0;
            tokenId2Address[1] = joyToken;
        }
    
        /**
         * @notice deposit token into the contract
         * @notice Be sure to Approve the contract to move your erc20 token
         * @param token The address of deposited token
         * @param amount The amount of token to deposit
         */
        function depositToken(address token, uint256 amount) external {
            require(amount > 0);
            require(tokenAddress2Id[token] != 0);
            addUser(msg.sender);
            require(ERC20(token).transferFrom(msg.sender, this, amount));
            balances[token][msg.sender] = balances[token][msg.sender].add(amount);
            Deposit(
                token,
                msg.sender,
                amount,
                balances[token][msg.sender]
            );
        }
    
        /**
         * @notice deposit Ether into the contract
         */
        function depositEther() external payable {
            require(msg.value > 0);
            addUser(msg.sender);
            balances[0][msg.sender] = balances[0][msg.sender].add(msg.value);
            Deposit(
                0,
                msg.sender,
                msg.value,
                balances[0][msg.sender]
            );
        }
    
        /**
         * @notice withdraw funds directly from contract
         * @notice must claim by lockme first, after a period of time it would be valid
         * @param token The address of withdrawed token, using address(0) to withdraw Ether
         * @param amount The amount of token to withdraw
         */
        function withdraw(address token, uint256 amount) external {
            require(amount > 0);
            require(getTime() > userLock[msg.sender] && userLock[msg.sender] != 0);
            balances[token][msg.sender] = balances[token][msg.sender].sub(amount);
            if (token == 0) {
                msg.sender.transfer(amount);
            } else {
                require(ERC20(token).transfer(msg.sender, amount));
            }
            Withdraw(
                token,
                msg.sender,
                amount,
                balances[token][msg.sender]
            );
        }
    
        /**
         * @notice This function is used to claim to withdraw the funds
         * @notice The matching server will automaticlly remove all un-touched orders
         * @notice After a period of time, the claimed user can withdraw funds directly from contract without admins involved.
         */
        function lockMe() external {
            require(userAddress2Id[msg.sender] != 0);
            userLock[msg.sender] = getTime() + lockPeriod;
            Lock(msg.sender, userLock[msg.sender]);
        }
    
        /**
         * @notice This function is used to revoke the claim of lockMe
         */
        function unlockMe() external {
            require(userAddress2Id[msg.sender] != 0);
            userLock[msg.sender] = 0;
            Lock(msg.sender, 0);
        }
    
        /**
         * @notice set tradeEventEnabled, only owner
         * @param enabled Set tradeEventEnabled if enabled
         */
        function setTradeEventEnabled(bool enabled) external onlyOwner {
            tradeEventEnabled = enabled;
        }
    
        /**
         * @notice add/remove a address to admin list, only owner
         * @param admin The address of the admin
         * @param isAdd Set the address's status in admin list
         */
        function addToAdmin(address admin, bool isAdd) external onlyOwner {
            isAdmin[admin] = isAdd;
        }
    
        /**
         * @notice collect the fee to owner's address, only owner
         */
        function collectFee(address token) external onlyOwner {
            uint256 amount = balances[token][joysoWallet];
            require(amount > 0);
            balances[token][joysoWallet] = 0;
            if (token == 0) {
                msg.sender.transfer(amount);
            } else {
                require(ERC20(token).transfer(msg.sender, amount));
            }
            Withdraw(
                token,
                joysoWallet,
                amount,
                0
            );
        }
    
        /**
         * @notice change lock period, only owner
         * @dev can change from 1 days to 30 days, initial is 30 days
         */
        function changeLockPeriod(uint256 periodInDays) external onlyOwner {
            require(periodInDays <= 30 && periodInDays >= 1);
            lockPeriod = periodInDays * 1 days;
        }
    
        /**
         * @notice add a new token into the token list, only admins
         * @dev index 0 & 1 are saved for Ether and JOY
         * @dev both index & token can not be redundant, and no removed mathod
         * @param tokenAddress token's address
         * @param index chosen index of the token
         */
        function registerToken(address tokenAddress, uint256 index) external onlyAdmin {
            require(index > 1);
            require(tokenAddress2Id[tokenAddress] == 0);
            require(tokenId2Address[index] == 0);
            tokenAddress2Id[tokenAddress] = index;
            tokenId2Address[index] = tokenAddress;
        }
    
        /**
         * @notice withdraw with admins involved, only admin
         * @param inputs array of inputs, must have 5 elements
         * @dev inputs encoding please reference github wiki
         */
        function withdrawByAdmin_Unau(uint256[] inputs) external onlyAdmin {
            uint256 amount = inputs[0];
            uint256 gasFee = inputs[1];
            uint256 data = inputs[2];
            uint256 paymentMethod = data & PAYMENT_METHOD_MASK;
            address token = tokenId2Address[(data & WITHDRAW_TOKEN_MASK) >> 32];
            address user = userId2Address[data & USER_MASK];
            bytes32 hash = keccak256(
                this,
                amount,
                gasFee,
                data & SIGN_MASK | uint256(token)
            );
            require(!usedHash[hash]);
            require(
                verify(
                    hash,
                    user,
                    uint8(data & V_MASK == 0 ? 27 : 28),
                    bytes32(inputs[3]),
                    bytes32(inputs[4])
                )
            );
    
            address gasToken = 0;
            if (paymentMethod == PAY_BY_JOY) { // pay fee by JOY
                gasToken = joyToken;
            } else if (paymentMethod == PAY_BY_TOKEN) { // pay fee by tx token
                gasToken = token;
            }
    
            if (gasToken == token) { // pay by ether or token
                balances[token][user] = balances[token][user].sub(amount.add(gasFee));
            } else {
                balances[token][user] = balances[token][user].sub(amount);
                balances[gasToken][user] = balances[gasToken][user].sub(gasFee);
            }
            balances[gasToken][joysoWallet] = balances[gasToken][joysoWallet].add(gasFee);
    
            usedHash[hash] = true;
    
            if (token == 0) {
                user.transfer(amount);
            } else {
                require(ERC20(token).transfer(user, amount));
            }
        }
    
        /**
         * @notice match orders with admins involved, only admin
         * @param inputs Array of input orders, each order have 6 elements. Inputs must conatin at least 2 orders.
         * @dev inputs encoding please reference github wiki
         */
        function matchByAdmin_TwH36(uint256[] inputs) external onlyAdmin {
            uint256 data = inputs[3];
            address user = userId2Address[data & USER_MASK];
            // check taker order nonce
            require(data >> 224 > userNonce[user]);
            address token;
            bool isBuy;
            (token, isBuy) = decodeOrderTokenAndIsBuy(data);
            bytes32 orderHash = keccak256(
                this,
                inputs[0],
                inputs[1],
                inputs[2],
                data & MATCH_SIGN_MASK | (isBuy ? ORDER_ISBUY : 0) | uint256(token)
            );
            require(
                verify(
                    orderHash,
                    user,
                    uint8(data & V_MASK == 0 ? 27 : 28),
                    bytes32(inputs[4]),
                    bytes32(inputs[5])
                )
            );
    
            uint256 tokenExecute = isBuy ? inputs[1] : inputs[0]; // taker order token execute
            tokenExecute = tokenExecute.sub(orderFills[orderHash]);
            require(tokenExecute != 0); // the taker order should remain something to trade
            uint256 etherExecute = 0;  // taker order ether execute
    
            isBuy = !isBuy;
            for (uint256 i = 6; i < inputs.length; i += 6) {
                //check price, maker price should lower than taker price
                require(tokenExecute > 0 && inputs[1].mul(inputs[i + 1]) <= inputs[0].mul(inputs[i]));
    
                data = inputs[i + 3];
                user = userId2Address[data & USER_MASK];
                // check maker order nonce
                require(data >> 224 > userNonce[user]);
                bytes32 makerOrderHash = keccak256(
                    this,
                    inputs[i],
                    inputs[i + 1],
                    inputs[i + 2],
                    data & MATCH_SIGN_MASK | (isBuy ? ORDER_ISBUY : 0) | uint256(token)
                );
                require(
                    verify(
                        makerOrderHash,
                        user,
                        uint8(data & V_MASK == 0 ? 27 : 28),
                        bytes32(inputs[i + 4]),
                        bytes32(inputs[i + 5])
                    )
                );
                (tokenExecute, etherExecute) = internalTrade(
                    inputs[i],
                    inputs[i + 1],
                    inputs[i + 2],
                    data,
                    tokenExecute,
                    etherExecute,
                    isBuy,
                    token,
                    0,
                    makerOrderHash
                );
            }
    
            isBuy = !isBuy;
            tokenExecute = isBuy ? inputs[1].sub(tokenExecute) : inputs[0].sub(tokenExecute);
            tokenExecute = tokenExecute.sub(orderFills[orderHash]);
            processTakerOrder(inputs[2], inputs[3], tokenExecute, etherExecute, isBuy, token, 0, orderHash);
        }
    
        /**
         * @notice match token orders with admins involved, only admin
         * @param inputs Array of input orders, each order have 6 elements. Inputs must conatin at least 2 orders.
         * @dev inputs encoding please reference github wiki
         */
        function matchTokenOrderByAdmin_k44j(uint256[] inputs) external onlyAdmin {
            address user = userId2Address[decodeOrderUserId(inputs[3])];
            // check taker order nonce
            require(inputs[3] >> 224 > userNonce[user]);
            address token;
            address base;
            bool isBuy;
            (token, base, isBuy) = decodeTokenOrderTokenAndIsBuy(inputs[3]);
            bytes32 orderHash = getTokenOrderDataHash(inputs, 0, inputs[3], token, base);
            require(
                verify(
                    orderHash,
                    user,
                    uint8(retrieveV(inputs[3])),
                    bytes32(inputs[4]),
                    bytes32(inputs[5])
                )
            );
            uint256 tokenExecute = isBuy ? inputs[1] : inputs[0]; // taker order token execute
            tokenExecute = tokenExecute.sub(orderFills[orderHash]);
            require(tokenExecute != 0); // the taker order should remain something to trade
            uint256 baseExecute = 0;  // taker order ether execute
    
            isBuy = !isBuy;
            for (uint256 i = 6; i < inputs.length; i += 6) {
                //check price, taker price should better than maker price
                require(tokenExecute > 0 && inputs[1].mul(inputs[i + 1]) <= inputs[0].mul(inputs[i]));
    
                user = userId2Address[decodeOrderUserId(inputs[i + 3])];
                // check maker order nonce
                require(inputs[i + 3] >> 224 > userNonce[user]);
                bytes32 makerOrderHash = getTokenOrderDataHash(inputs, i, inputs[i + 3], token, base);
                require(
                    verify(
                        makerOrderHash,
                        user,
                        uint8(retrieveV(inputs[i + 3])),
                        bytes32(inputs[i + 4]),
                        bytes32(inputs[i + 5])
                    )
                );
                (tokenExecute, baseExecute) = internalTrade(
                    inputs[i],
                    inputs[i + 1],
                    inputs[i + 2],
                    inputs[i + 3],
                    tokenExecute,
                    baseExecute,
                    isBuy,
                    token,
                    base,
                    makerOrderHash
                );
            }
    
            isBuy = !isBuy;
            tokenExecute = isBuy ? inputs[1].sub(tokenExecute) : inputs[0].sub(tokenExecute);
            tokenExecute = tokenExecute.sub(orderFills[orderHash]);
            processTakerOrder(inputs[2], inputs[3], tokenExecute, baseExecute, isBuy, token, base, orderHash);
        }
    
        /**
         * @notice update user on-chain nonce with admins involved, only admin
         * @param inputs Array of input data, must have 4 elements.
         * @dev inputs encoding please reference github wiki
         */
        function cancelByAdmin(uint256[] inputs) external onlyAdmin {
            uint256 data = inputs[1];
            uint256 nonce = data >> 224;
            address user = userId2Address[data & USER_MASK];
            require(nonce > userNonce[user]);
            uint256 gasFee = inputs[0];
            require(
                verify(
                    keccak256(this, gasFee, data & SIGN_MASK),
                    user,
                    uint8(retrieveV(data)),
                    bytes32(inputs[2]),
                    bytes32(inputs[3])
                )
            );
    
            // update balance
            address gasToken = 0;
            if (data & PAYMENT_METHOD_MASK == PAY_BY_JOY) {
                gasToken = joyToken;
            }
            require(balances[gasToken][user] >= gasFee);
            balances[gasToken][user] = balances[gasToken][user].sub(gasFee);
            balances[gasToken][joysoWallet] = balances[gasToken][joysoWallet].add(gasFee);
    
            // update user nonce
            userNonce[user] = nonce;
        }
    
        /**
         * @notice batch send the current balance to the new version contract
         * @param inputs Array of input data
         * @dev inputs encoding please reference github wiki
         */
        function migrateByAdmin_DQV(uint256[] inputs) external onlyAdmin {
            uint256 data = inputs[2];
            address token = tokenId2Address[(data & WITHDRAW_TOKEN_MASK) >> 32];
            address newContract = address(inputs[0]);
            for (uint256 i = 1; i < inputs.length; i += 4) {
                uint256 gasFee = inputs[i];
                data = inputs[i + 1];
                address user = userId2Address[data & USER_MASK];
                bytes32 hash = keccak256(
                    this,
                    gasFee,
                    data & SIGN_MASK | uint256(token),
                    newContract
                );
                require(
                    verify(
                        hash,
                        user,
                        uint8(data & V_MASK == 0 ? 27 : 28),
                        bytes32(inputs[i + 2]),
                        bytes32(inputs[i + 3])
                    )
                );
                if (gasFee > 0) {
                    uint256 paymentMethod = data & PAYMENT_METHOD_MASK;
                    if (paymentMethod == PAY_BY_JOY) {
                        balances[joyToken][user] = balances[joyToken][user].sub(gasFee);
                        balances[joyToken][joysoWallet] = balances[joyToken][joysoWallet].add(gasFee);
                    } else if (paymentMethod == PAY_BY_TOKEN) {
                        balances[token][user] = balances[token][user].sub(gasFee);
                        balances[token][joysoWallet] = balances[token][joysoWallet].add(gasFee);
                    } else {
                        balances[0][user] = balances[0][user].sub(gasFee);
                        balances[0][joysoWallet] = balances[0][joysoWallet].add(gasFee);
                    }
                }
                uint256 amount = balances[token][user];
                balances[token][user] = 0;
                if (token == 0) {
                    Migratable(newContract).migrate.value(amount)(user, amount, token);
                } else {
                    ERC20(token).approve(newContract, amount);
                    Migratable(newContract).migrate(user, amount, token);
                }
            }
        }
    
        /**
         * @notice transfer token from admin to users
         * @param token address of token
         * @param account receiver's address
         * @param amount amount to transfer
         */
        function transferForAdmin(address token, address account, uint256 amount) onlyAdmin external {
            require(tokenAddress2Id[token] != 0);
            require(userAddress2Id[msg.sender] != 0);
            addUser(account);
            balances[token][msg.sender] = balances[token][msg.sender].sub(amount);
            balances[token][account] = balances[token][account].add(amount);
        }
    
        /**
         * @notice get balance information
         * @param token address of token
         * @param account address of user
         */
        function getBalance(address token, address account) external view returns (uint256) {
            return balances[token][account];
        }
    
        /**
         * @dev get tokenId and check the order is a buy order or not, internal
         *      tokenId take 4 bytes
         *      isBuy is true means this order is buying token
         */
        function decodeOrderTokenAndIsBuy(uint256 data) internal view returns (address token, bool isBuy) {
            uint256 tokenId = (data & TOKEN_SELL_MASK) >> 48;
            if (tokenId == 0) {
                token = tokenId2Address[(data & TOKEN_BUY_MASK) >> 32];
                isBuy = true;
            } else {
                token = tokenId2Address[tokenId];
            }
        }
    
        /**
         * @dev decode token oreder data, internal
         */
        function decodeTokenOrderTokenAndIsBuy(uint256 data) internal view returns (address token, address base, bool isBuy) {
            isBuy = data & IS_BUY_MASK == ORDER_ISBUY;
            if (isBuy) {
                token = tokenId2Address[(data & TOKEN_BUY_MASK) >> 32];
                base = tokenId2Address[(data & TOKEN_SELL_MASK) >> 48];
            } else {
                token = tokenId2Address[(data & TOKEN_SELL_MASK) >> 48];
                base = tokenId2Address[(data & TOKEN_BUY_MASK) >> 32];
            }
        }
    
        function getTime() internal view returns (uint256) {
            return now;
        }
    
        /**
         * @dev get token order's hash for user to sign, internal
         * @param inputs forword tokenOrderMatch's input to this function
         * @param offset offset of the order in inputs
         */
        function getTokenOrderDataHash(uint256[] inputs, uint256 offset, uint256 data, address token, address base) internal view returns (bytes32) {
            return keccak256(
                this,
                inputs[offset],
                inputs[offset + 1],
                inputs[offset + 2],
                data & SIGN_MASK | uint256(token),
                base,
                (data & TOKEN_JOY_PRICE_MASK) >> 64
            );
        }
    
        /**
         * @dev check if the provided signature is valid, internal
         * @param hash signed information
         * @param sender signer address
         * @param v sig_v
         * @param r sig_r
         * @param s sig_s
         */
        function verify(bytes32 hash, address sender, uint8 v, bytes32 r, bytes32 s) internal pure returns (bool) {
            return ecrecover(keccak256("\x19Ethereum Signed Message:\n32", hash), v, r, s) == sender;
        }
    
        /**
         * @dev give a new user an id, intrnal
         */
        function addUser(address _address) internal {
            if (userAddress2Id[_address] != 0) {
                return;
            }
            userCount += 1;
            userAddress2Id[_address] = userCount;
            userId2Address[userCount] = _address;
            NewUser(_address, userCount);
        }
    
        function processTakerOrder(
            uint256 gasFee,
            uint256 data,
            uint256 tokenExecute,
            uint256 baseExecute,
            bool isBuy,
            address token,
            address base,
            bytes32 orderHash
        )
            internal
        {
            uint256 fee = calculateFee(gasFee, data, baseExecute, orderHash, true, base == 0);
            updateUserBalance(data, isBuy, baseExecute, tokenExecute, fee, token, base);
            orderFills[orderHash] = orderFills[orderHash].add(tokenExecute);
            if (tradeEventEnabled) {
                TradeSuccess(userId2Address[data & USER_MASK], baseExecute, tokenExecute, isBuy, fee);
            }
        }
    
        function internalTrade(
            uint256 amountSell,
            uint256 amountBuy,
            uint256 gasFee,
            uint256 data,
            uint256 _remainingToken,
            uint256 _baseExecute,
            bool isBuy,
            address token,
            address base,
            bytes32 orderHash
        )
            internal returns (uint256 remainingToken, uint256 baseExecute)
        {
            uint256 tokenGet = calculateTokenGet(amountSell, amountBuy, _remainingToken, isBuy, orderHash);
            uint256 baseGet = calculateBaseGet(amountSell, amountBuy, isBuy, tokenGet);
            uint256 fee = calculateFee(gasFee, data, baseGet, orderHash, false, base == 0);
            updateUserBalance(data, isBuy, baseGet, tokenGet, fee, token, base);
            orderFills[orderHash] = orderFills[orderHash].add(tokenGet);
            remainingToken = _remainingToken.sub(tokenGet);
            baseExecute = _baseExecute.add(baseGet);
            if (tradeEventEnabled) {
                TradeSuccess(
                    userId2Address[data & USER_MASK],
                    baseGet,
                    tokenGet,
                    isBuy,
                    fee
                );
            }
        }
    
        function updateUserBalance(
            uint256 data,
            bool isBuy,
            uint256 baseGet,
            uint256 tokenGet,
            uint256 fee,
            address token,
            address base
        )
            internal
        {
            address user = userId2Address[data & USER_MASK];
            uint256 baseFee = fee;
            uint256 joyFee = 0;
            if ((base == 0 ? (data & JOY_PRICE_MASK) >> 164 : (data & TOKEN_JOY_PRICE_MASK) >> 64) != 0) {
                joyFee = fee;
                baseFee = 0;
            }
    
            if (isBuy) { // buy token, sell ether
                balances[base][user] = balances[base][user].sub(baseGet).sub(baseFee);
                balances[token][user] = balances[token][user].add(tokenGet);
            } else {
                balances[base][user] = balances[base][user].add(baseGet).sub(baseFee);
                balances[token][user] = balances[token][user].sub(tokenGet);
            }
    
            if (joyFee != 0) {
                balances[joyToken][user] = balances[joyToken][user].sub(joyFee);
                balances[joyToken][joysoWallet] = balances[joyToken][joysoWallet].add(joyFee);
            } else {
                balances[base][joysoWallet] = balances[base][joysoWallet].add(baseFee);
            }
        }
    
        function calculateFee(
            uint256 gasFee,
            uint256 data,
            uint256 baseGet,
            bytes32 orderHash,
            bool isTaker,
            bool isEthOrder
        )
            internal view returns (uint256)
        {
            uint256 fee = orderFills[orderHash] == 0 ? gasFee : 0;
            uint256 txFee = baseGet.mul(isTaker ? (data & TAKER_FEE_MASK) >> 208 : (data & MAKER_FEE_MASK) >> 192) / 10000;
            uint256 joyPrice = isEthOrder ? (data & JOY_PRICE_MASK) >> 164 : (data & TOKEN_JOY_PRICE_MASK) >> 64;
            if (joyPrice != 0) {
                txFee = isEthOrder ? txFee / (10 ** 5) / joyPrice : txFee * (10 ** 12) / joyPrice;
            }
            return fee.add(txFee);
        }
    
        function calculateBaseGet(
            uint256 amountSell,
            uint256 amountBuy,
            bool isBuy,
            uint256 tokenGet
        )
            internal pure returns (uint256)
        {
            return isBuy ? tokenGet.mul(amountSell) / amountBuy : tokenGet.mul(amountBuy) / amountSell;
        }
    
        function calculateTokenGet(
            uint256 amountSell,
            uint256 amountBuy,
            uint256 remainingToken,
            bool isBuy,
            bytes32 orderHash
        )
            internal view returns (uint256)
        {
            uint256 makerRemainingToken = isBuy ? amountBuy : amountSell;
            makerRemainingToken = makerRemainingToken.sub(orderFills[orderHash]);
            require(makerRemainingToken > 0); // the maker order should remain something to trade
            return makerRemainingToken >= remainingToken ? remainingToken : makerRemainingToken;
        }
    }