contract DolomiteMarginProtocol is IDyDxCallee, IBrokerDelegate, IDyDxExchangeWrapper, DolomiteMarginReentrancyGuard, Ownable {
    using Order for *;
    using Activity for *;
    using LoopringTradeDelegateHelper for ILoopringTradeDelegate;
    using SafeERC20 for IERC20;
    using SafeMath for *;

    //        bytes32 public constant DEPOSIT_COLLATERAL_TYPE_HASH = keccak256("DepositCollateral(address signer,uint256 positionId,uint256 tokenId,uint256 amount,uint256 feeAmount,address feeRecipient,uint256 expiry,uint256 nonce)");
    bytes32 public constant DEPOSIT_COLLATERAL_TYPE_HASH = 0x3e5ecf67633bf55e5afafd4ed3ca3ce81df8fe6e1767e986edc5e247c0019d27;

    //        bytes32 public constant WITHDRAW_COLLATERAL_TYPE_HASH = keccak256("WithdrawCollateral(address signer,uint256 positionId,uint256 tokenId,uint256 amount,uint256 feeAmount,address feeRecipient,uint256 expiry,uint256 nonce)");
    bytes32 public constant WITHDRAW_COLLATERAL_TYPE_HASH = 0xf4f01e4a789a88db3d7581d902fca290c3d0ef9a0974ee9c5f6ac11c86056e7e;

    bytes32 public domainSeparator;

    struct RelayParams {
        uint[] relevantOrderIndices;
        uint[] relevantMarketIds;
        Activity.ActivityArg[] activityArgs;
        address dustCollector;
    }

    address public DYDX_EXPIRY_ADDRESS;
    IDyDxProtocol public DYDX_PROTOCOL;
    IRingSubmitter public LOOPRING_PROTOCOL;
    IDepositContractRegistry public DEPOSIT_CONTRACT_REGISTRY;
    ILoopringTradeDelegate public TRADE_DELEGATE;

    constructor(
        address dydxProtocol,
        address payable loopringProtocol,
        address dydxExpiry,
        address depositContractRegistry
    ) public {
        DYDX_PROTOCOL = IDyDxProtocol(dydxProtocol);
        LOOPRING_PROTOCOL = IRingSubmitter(loopringProtocol);
        TRADE_DELEGATE = ILoopringTradeDelegate(LOOPRING_PROTOCOL.delegateAddress());
        DEPOSIT_CONTRACT_REGISTRY = IDepositContractRegistry(depositContractRegistry);
        DYDX_EXPIRY_ADDRESS = dydxExpiry;

        uint256 chainId;
        assembly {chainId := chainid()}

        domainSeparator = keccak256(abi.encode(
                keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                keccak256(bytes("Dolomite Margin")),
                keccak256(bytes(/* version */ "1")),
                chainId,
                address(this)
            ));
    }

    // =================================

    mapping(bytes32 => bool) private _orderHasDeposited;
    mapping(bytes32 => mapping(address => uint)) private _runtimeIncomingAmount;
    mapping(address => uint) private _nonces;

    function nonceOf(address user) public view returns (uint) {
        return _nonces[user];
    }

    function withdrawDust(address token) public onlyOwner {
        IERC20(token).safeTransfer(owner(), IERC20(token).balanceOf(address(this)));
    }

    function depositCollateral(uint positionId, uint tokenId, uint amount) public singleEntry {
        TRADE_DELEGATE.transferTokenFrom(
            DYDX_PROTOCOL.getMarketTokenAddress(tokenId),
            msg.sender,
            address(this),
            amount
        );

        DydxPosition.Info[] memory infos = new DydxPosition.Info[](1);
        infos[0] = DydxPosition.Info(msg.sender, positionId);

        DydxActions.ActionArgs[] memory args = new DydxActions.ActionArgs[](1);
        args[0] = DydxActionBuilder.Deposit(0, tokenId, amount, /* from */ address(this));

        DYDX_PROTOCOL.operate(infos, args);
    }

    function withdrawCollateral(uint positionId, uint tokenId, uint amount) public singleEntry {
        DydxPosition.Info[] memory infos = new DydxPosition.Info[](1);
        infos[0] = DydxPosition.Info(msg.sender, positionId);

        DydxActions.ActionArgs[] memory args = new DydxActions.ActionArgs[](1);
        if (amount == uint(- 1)) {
            args[0] = DydxActionBuilder.WithdrawAll(0, tokenId, msg.sender);
        } else {
            args[0] = DydxActionBuilder.Withdraw(0, tokenId, amount, msg.sender);
        }

        DYDX_PROTOCOL.operate(infos, args);
    }

    function depositCollateralViaGaslessRequest(
        address signer,
        uint positionId,
        uint tokenId,
        uint amount,
        uint feeAmount,
        address feeRecipient,
        uint expiry,
        uint nonce,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public singleEntry {
        require(amount >= feeAmount, "FEE_TOO_LARGE");
        validateGaslessRequest(
            DEPOSIT_COLLATERAL_TYPE_HASH,
            signer,
            positionId,
            tokenId,
            amount,
            feeAmount,
            feeRecipient,
            expiry,
            nonce,
            v,
            r,
            s
        );

        // These functions were created to help avoid the infamous "stack too deep" error.
        performDolomiteDirectDepositTransferIn(signer, tokenId, amount);
        payFeesForDolomiteDirectDepositIfNecessary(feeAmount, feeRecipient, tokenId);
        performDolomiteDirectDeposit(signer, positionId, tokenId, amount.sub(feeAmount));
    }

    function withdrawCollateralViaGaslessRequest(
        address signer,
        uint positionId,
        uint tokenId,
        uint amount,
        uint feeAmount,
        address feeRecipient,
        uint expiry,
        uint nonce,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public singleEntry {
        require(amount >= feeAmount, "FEE_TOO_LARGE");
        validateGaslessRequest(
            WITHDRAW_COLLATERAL_TYPE_HASH,
            signer,
            positionId,
            tokenId,
            amount,
            feeAmount,
            feeRecipient,
            expiry,
            nonce,
            v,
            r,
            s
        );

        // This is function was created to help avoid the infamous "stack too deep" error.
        performDolomiteDirectWithdraw(signer, positionId, tokenId, amount);
        payFeesForDolomiteDirectWithdrawalIfNecessary(signer, feeAmount, feeRecipient, tokenId);
    }

    /*
     * If orders in a Loopring settlement require interaction(s) with dYdX in order to
     * trade on margin, loan money to perform arbitrage or perform liquidations, they can
     * be submitted here. The orders that need dYdX are registered along with other relevant
     * information in the RelayParams parameter by the relay.
     */
    function submitRingsThroughDyDx(
        bytes memory ringData,
        RelayParams memory params
    ) public singleEntry {
        Order.Info[] memory orders = ringData.decodeRawOrders(params.relevantOrderIndices);
        submitRingsThroughDyDx(ringData, params, orders, /* isTypeSafe */ false);
    }

    /*
     * If orders in a Loopring settlement require interaction(s) with dYdX in order to
     * trade on margin, loan money to perform arbitrage or perform liquidations, they can
     * be submitted here. The orders that need dYdX are registered along with other relevant
     * information in the RelayParams parameter by the relay.
     */
    function submitRingsThroughDyDx(
        bytes memory ringData,
        RelayParams memory params,
        Order.Info[] memory orders,
        bool isTypeSafe
    ) internal {
        RunTime.Context memory ctx = _createRuntimeContext(orders, params);
        _registerActivities(ctx, params.activityArgs);
        _resolveDepositFlagTriggers(ctx);
        _performTokenTransfers(ctx);

        (
        DydxPosition.Info[] memory positions,
        DydxActions.ActionArgs[] memory actions
        ) = _generateDydxPerformParams(ctx, ringData, isTypeSafe);

        DYDX_PROTOCOL.operate(positions, actions);

        _clearRuntime(ctx);
    }

    function _createRuntimeContext(Order.Info[] memory orders, RelayParams memory params)
    internal
    view
    returns (RunTime.Context memory ctx)
    {
        uint numActivities = params.activityArgs.length;

        uint totalNumMarkets = DYDX_PROTOCOL.getNumMarkets();
        ctx.marketTokenAddress = new address[](totalNumMarkets);
        for (uint i = 0; i < params.relevantMarketIds.length; i++) {
            ctx.marketTokenAddress[params.relevantMarketIds[i]] = DYDX_PROTOCOL.getMarketTokenAddress(params.relevantMarketIds[i]);
        }

        ctx.self = address(this);
        ctx.orders = orders;
        ctx.depositContractRegistry = DEPOSIT_CONTRACT_REGISTRY;
        ctx.dydxExpiryContractAddress = DYDX_EXPIRY_ADDRESS;
        ctx.depositFlagTriggers = new bytes32[](numActivities);
        ctx.tokenTransfers = new RunTime.TokenTransfer[](numActivities);
        ctx.dydxPositions = new DydxPosition.Info[](numActivities * 3);
        ctx.dydxBeforeActions = new DydxActions.ActionArgs[](numActivities * 3);
        ctx.dydxAfterActions = new DydxActions.ActionArgs[](numActivities * 3);

        return ctx;
    }

    function _registerActivities(RunTime.Context memory ctx, Activity.ActivityArg[] memory activityArgs) internal view {
        for (uint i = 0; i < activityArgs.length; i++) {
            Activity.ActivityArg memory arg = activityArgs[i];
            ctx.registerActivity(arg.activityType, arg.encodedFields);
        }
    }

    function _resolveDepositFlagTriggers(RunTime.Context memory ctx) internal {
        for (uint i = 0; i < ctx.numDepositFlagTriggers; i++) {
            bytes32 orderHash = ctx.depositFlagTriggers[i];
            require(_orderHasDeposited[orderHash] == false, "ORDER_DEPOSIT_REJECTED: deposit already performed");
            _orderHasDeposited[orderHash] = true;
        }
    }

    function _performTokenTransfers(RunTime.Context memory ctx) internal {
        for (uint i = 0; i < ctx.numTokenTransfers; i++) {
            RunTime.TokenTransfer memory transfer = ctx.tokenTransfers[i];
            if (transfer.isUsingDepositContract) {
                IDolomiteDirect dolomiteDirect = IDolomiteDirect(DEPOSIT_CONTRACT_REGISTRY.versionOf(transfer.from));
                dolomiteDirect.brokerMarginRequestApproval(
                    transfer.from,
                    transfer.token,
                    transfer.amount
                );
                TRADE_DELEGATE.transferTokenFrom(
                    transfer.token,
                    address(dolomiteDirect),
                    transfer.to,
                    transfer.amount
                );
            } else {
                TRADE_DELEGATE.transferTokenFrom(
                    transfer.token,
                    transfer.from,
                    transfer.to,
                    transfer.amount
                );
            }
        }
    }

    function _generateDydxPerformParams(RunTime.Context memory ctx, bytes memory ringData, bool isTypeSafe)
    internal
    pure
    returns (DydxPosition.Info[] memory positions, DydxActions.ActionArgs[] memory actions)
    {
        positions = new DydxPosition.Info[](ctx.numDydxPositions + 1);
        actions = new DydxActions.ActionArgs[](ctx.numDydxBeforeActions + ctx.numDydxAfterActions + 1);

        // Trim positions array & insert before actions to beginning of actions array
        for (uint i = 0; i < ctx.numDydxPositions; i++) positions[i] = ctx.dydxPositions[i];
        for (uint j = 0; j < ctx.numDydxBeforeActions; j++) actions[j] = ctx.dydxBeforeActions[j];

        // Insert Loopring settlement action after the "before" actions
        positions[ctx.numDydxPositions] = DydxPosition.Info(ctx.self, 123456789);
        actions[ctx.numDydxBeforeActions] = DydxActionBuilder.LoopringSettlement({
            settlementData : abi.encode(isTypeSafe, ringData),
            settlementCaller : ctx.self,
            positionIndex : ctx.numDydxPositions
            });

        // Insert all of the after actions after Loopring settlement is to take place
        for (uint k = 0; k < ctx.numDydxAfterActions; k++) {
            actions[k + ctx.numDydxBeforeActions + 1] = ctx.dydxAfterActions[k];
        }
    }

    function _clearRuntime(RunTime.Context memory ctx) internal {
        for (uint i = 0; i < ctx.orders.length; i++) {
            bytes32 orderHash = ctx.orders[i].orderHash;
            for (uint j = 0; j < ctx.marketTokenAddress.length; j++) {
                _runtimeIncomingAmount[orderHash][ctx.marketTokenAddress[j]] = 0;
            }
        }
    }

    // =================================
    // Loopring Settlement & dYdX Action Callbacks

    /*
     * @Implements IDyDxCallee
     *
     * dYdX is instructed to perform all of the necessary actions for
     * margin order settlement (and loan/liquidation settlement) to succeed.
     * After all of these actions, an action is performed that calls this function,
     * which performs trade settlement through Loopring v2.3
     */
    function callFunction(
        address sender,
        DydxPosition.Info memory accountInfo,
        bytes memory data
    ) public noEntry {
        require(msg.sender == address(DYDX_PROTOCOL), "INVALID_CALLER: IDyDxCallee caller must be dYdX protocol");
        (bool isTypeSafe, bytes memory ringData) = abi.decode(data, (bool, bytes));
        if (isTypeSafe) {
            revert("Submitting rings with type safety is not enabled!");
            //      LOOPRING_PROTOCOL.submitRings(abi.decode(ringData, (Data.SubmitRingsRequest)));
        } else {
            LOOPRING_PROTOCOL.submitRings(ringData);
        }
    }

    /*
     * @Implements ILoopringBrokerDelegate
     *
     * Loopring will request the available balance of the order owner if the broker. The amount of funds borrowed is
     * baked into the size of the order. Thus, returning an actual balance is redundant.
     */
    function brokerBalanceOf(address user, address token) public view returns (uint) {
        return uint(- 1);
    }

    /*
     * @Implements ILoopringBrokerDelegate
     *
     * Loopring will then request approval to transfer the borrowed amount to settle the
     * order with the borrowed funds. Loopring notifies this contract how much the order
     * has been filled and requires that the funds are sent into itself (this). The exact
     * amount of received tokens is stored for later use.
     */
    function brokerRequestAllowance(BrokerData.BrokerApprovalRequest memory request) public noEntry returns (bool) {
        require(msg.sender == address(TRADE_DELEGATE), "INVALID_CALLER: Caller of broker impl must be Loopring Delegate");

        for (uint i = 0; i < request.orders.length; i++) {
            BrokerData.BrokerOrder memory order = request.orders[i];
            require(order.tokenRecipient == address(this), "INVALID_RECIPIENT: Token recipient must be set to margin protocol");
            require(order.requestedFeeAmount == 0, "INVALID_ORDER_FEE: FeeToken must be in tokenB of Loopring order");

            _runtimeIncomingAmount[order.orderHash][request.tokenB] = order.fillAmountB;
        }

        return false;
    }

    function onOrderFillReport(BrokerData.BrokerInterceptorReport memory fillReport) public {
        // No op - do nothing
    }

    /*
     * @Implements IDyDxExchangeWrapper
     *
     * Neat "hack": if you want to orchestrate a deposit for an unknown amount
     * you can use a SellAction to do it. Have dYdX "sell" 1 wei and, in the
     * implementation of the IExchangeWrapper's exchange function, return an amount
     * and it will be deposited into the dYdX position in full! 1 wei is burned unfortunately,
     * but who really cares.
     *
     * That amount we "stored for later use" is returned so that all of the purchased tokens
     * are deposited into dYdX in full.
     */
    function exchange(
        address tradeOriginator,
        address receiver,
        address makerToken,
        address takerToken,
        uint256 requestedFillAmount,
        bytes memory orderData
    ) public noEntry returns (uint256) {
        require(msg.sender == address(DYDX_PROTOCOL), "INVALID_EXCHANGE_CALLER");
        bytes32 orderHash = abi.decode(orderData, (bytes32));
        return _runtimeIncomingAmount[orderHash][makerToken];
    }

    // =================================
    // Administrative

    function enableToken(address tokenAddress) public {
        IERC20(tokenAddress).approve(address(TRADE_DELEGATE), uint(- 1));
        IERC20(tokenAddress).approve(address(DYDX_PROTOCOL), uint(- 1));
    }

    function enableTokens(address[] calldata tokenAddresses) external {
        for (uint i = 0; i < tokenAddresses.length; i++) {
            enableToken(tokenAddresses[i]);
        }
    }

    // =================================
    // Private

    function validateGaslessRequest(
        bytes32 typeHash,
        address signer,
        uint positionId,
        uint tokenId,
        uint amount,
        uint feeAmount,
        address feeRecipient,
        uint expiry,
        uint nonce,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) private {
        bytes32 digest = keccak256(
            abi.encodePacked(
                "\x19\x01",
                domainSeparator,
                keccak256(abi.encode(typeHash, signer, positionId, tokenId, amount, feeAmount, feeRecipient, expiry, nonce))
            )
        );

        require(signer == ecrecover(digest, v, r, s), "INVALID_SIGNATURE");
        require(expiry == 0 || now <= expiry, "REQUEST_EXPIRED");
        require(nonce == _nonces[signer], "INVALID_NONCE");
        if (feeAmount > 0) {
            require(feeRecipient != address(0x0), "INVALID_FEE_ADDRESS");
        }

        _nonces[signer] += 1;
    }

    function payFeesForDolomiteDirectDepositIfNecessary(
        uint feeAmount,
        address feeRecipient,
        uint tokenId
    ) private {
        if (feeAmount > 0) {
            require(feeRecipient != address(0x0), "INVALID_FEE_RECIPIENT");
            address token = DYDX_PROTOCOL.getMarketTokenAddress(tokenId);
            IERC20(token).safeTransfer(feeRecipient, feeAmount);
        }
    }

    function performDolomiteDirectDepositTransferIn(
        address signer,
        uint tokenId,
        uint amount
    )
    private {
        IDolomiteDirect dolomiteDirect = IDolomiteDirect(DEPOSIT_CONTRACT_REGISTRY.versionOf(signer));
        address token = DYDX_PROTOCOL.getMarketTokenAddress(tokenId);
        dolomiteDirect.brokerMarginRequestApproval(signer, token, amount);
        TRADE_DELEGATE.transferTokenFrom(
            token,
            address(dolomiteDirect),
            address(this),
            amount
        );
    }

    function performDolomiteDirectDeposit(
        address signer,
        uint positionId,
        uint tokenId,
        uint amountLessFee
    )
    private {
        address trader = DEPOSIT_CONTRACT_REGISTRY.depositAddressOf(signer);

        DydxPosition.Info[] memory infos = new DydxPosition.Info[](1);
        infos[0] = DydxPosition.Info(trader, positionId);

        DydxActions.ActionArgs[] memory args = new DydxActions.ActionArgs[](1);
        args[0] = DydxActionBuilder.Deposit(0, tokenId, amountLessFee, /* from */ address(this));

        DYDX_PROTOCOL.operate(infos, args);
    }

    function performDolomiteDirectWithdraw(
        address signer,
        uint positionId,
        uint tokenId,
        uint amount
    )
    private {
        address trader = DEPOSIT_CONTRACT_REGISTRY.depositAddressOf(signer);

        DydxPosition.Info[] memory infos = new DydxPosition.Info[](1);
        infos[0] = DydxPosition.Info(trader, positionId);

        // We withdraw to the trader's deposit contract so we can later pull the fee, if there is one. Reason being,
        // there may be leftover funds in this contract, so we cannot trust the values/balances presented in here.
        // Therefore, it's best to just send it to the trader, and then pull what's needed afterward.
        DydxActions.ActionArgs[] memory args = new DydxActions.ActionArgs[](1);
        if (amount == uint(- 1)) {
            args[0] = DydxActionBuilder.WithdrawAll(0, tokenId, trader);
        } else {
            args[0] = DydxActionBuilder.Withdraw(0, tokenId, amount, trader);
        }

        DYDX_PROTOCOL.operate(infos, args);
    }

    function payFeesForDolomiteDirectWithdrawalIfNecessary(
        address signer,
        uint feeAmount,
        address feeRecipient,
        uint tokenId
    ) private {
        if (feeAmount > 0) {
            require(feeRecipient != address(0x0), "INVALID_FEE_RECIPIENT");

            address dolomiteDirect = DEPOSIT_CONTRACT_REGISTRY.versionOf(signer);
            address token = DYDX_PROTOCOL.getMarketTokenAddress(tokenId);

            IDolomiteDirect(dolomiteDirect).brokerMarginRequestApproval(
                signer,
                token,
                feeAmount
            );
            TRADE_DELEGATE.transferTokenFrom(
                token,
                dolomiteDirect,
                feeRecipient,
                feeAmount
            );
        }
    }

}
/*
 * @title DolomiteMarginV2
 *
 * Allows Loopring v2.3 orders to trade on margin, loan funds with 0 collateral to perform 
 * arbitrage and liquidate under-collateralized positions through the dYdX protocol.
 *
 * Users sign orders with the broker set to this contract, so orders must be submitted
 * through this contract, ensuring that margin orders can only be settled as margin orders.
 * Users also sign other information in the extraData (transferDataS) field of the order 
 * which is used by this contract. The information provided by the relay is checked
 * against this information signed by the user, and is also verified by Loopring v2.3 and dYdX.
 */
function submitRingsThroughDyDx(
    bytes memory ringData,
    RelayParams memory params
) public singleEntry {
    Order.Info[] memory orders = ringData.decodeRawOrders(params.relevantOrderIndices);
    submitRingsThroughDyDx(ringData, params, orders, /* isTypeSafe */ false);
}

/*
 * If orders in a Loopring settlement require interaction(s) with dYdX in order to
 * trade on margin, loan money to perform arbitrage or perform liquidations, they can
 * be submitted here. The orders that need dYdX are registered along with other relevant
 * information in the RelayParams parameter by the relay.
 */