ETH Price: $2,077.34 (-1.22%)

Transaction Decoder

Block:
10291763 at Jun-18-2020 08:09:25 PM +UTC
Transaction Fee:
0.020930087489345138 ETH $43.48
Gas Used:
842,257 Gas / 24.850001234 Gwei

Emitted Events:

119 SoloMargin.0x91b01baeee3a24b590d112613814d86801005c7ef9353e7fc1eaeaf33ccf83b0( 0x91b01baeee3a24b590d112613814d86801005c7ef9353e7fc1eaeaf33ccf83b0, 000000000000000000000000f809e07870dca762b9536d61a4fbef1a17178092 )
120 SoloMargin.0xf4626fd1187f91e6761ffb8a6ac3e8d9235a4a92da54e43feb0c57c4a4a322ab( 0xf4626fd1187f91e6761ffb8a6ac3e8d9235a4a92da54e43feb0c57c4a4a322ab, 0x0000000000000000000000000000000000000000000000000000000000000003, 0000000000000000000000000000000000000000000000000e5fd99779ee4222, 0000000000000000000000000000000000000000000000000e3cf0b9c7e9ee0f, 000000000000000000000000000000000000000000000000000000005eebc9f5 )
121 SoloMargin.0xf4626fd1187f91e6761ffb8a6ac3e8d9235a4a92da54e43feb0c57c4a4a322ab( 0xf4626fd1187f91e6761ffb8a6ac3e8d9235a4a92da54e43feb0c57c4a4a322ab, 0x0000000000000000000000000000000000000000000000000000000000000002, 0000000000000000000000000000000000000000000000000eed4ad54462211d, 0000000000000000000000000000000000000000000000000e7aab3c7344fc4e, 000000000000000000000000000000000000000000000000000000005eebc9f5 )
122 SoloMargin.0xf4626fd1187f91e6761ffb8a6ac3e8d9235a4a92da54e43feb0c57c4a4a322ab( 0xf4626fd1187f91e6761ffb8a6ac3e8d9235a4a92da54e43feb0c57c4a4a322ab, 0x0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000e09693b7c115e60, 0000000000000000000000000000000000000000000000000de63968e176df80, 000000000000000000000000000000000000000000000000000000005eebc9f5 )
123 CanonicalOrders.LogCanonicalOrderFilled( orderHash=C7FF9A50DAEACA279C5686E03B3BD53ED2BFC8BE7BB83E94E22D3CE8E263FB91, orderMaker=0x66215D23...2AB384bCE, fillAmount=67000000000000000000000, triggerPrice=0, orderFlags=19D47E444183D9BFE3EEE582546E300D3E8A18F70C84DC4C01EE133D848173D1, fill=[{name:price, type:uint256, order:1, indexed:false, value:1010449, valueString:1010449}, {name:fee, type:uint128, order:2, indexed:false, value:450000000000000, valueString:450000000000000}, {name:isNegativeFee, type:bool, order:3, indexed:false, value:false, valueString:False}] )
124 SoloMargin.0x54d4cc60cf7d570631cc1a58942812cb0fc461713613400f56932040c3497d19( 0x54d4cc60cf7d570631cc1a58942812cb0fc461713613400f56932040c3497d19, 0x000000000000000000000000f809e07870dca762b9536d61a4fbef1a17178092, 0x00000000000000000000000066215d23b8a247c80c2d1b7bef4befc2ab384bce, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000003, 0000000000000000000000000000000000000000000000000000000000000002, 0000000000000000000000000000000000000000000000000000000000000000, 000000000000000000000000000000000000000000000e3013c87ac943e00000, 0000000000000000000000000000000000000000000000000000000000000000, 000000000000000000000000000000000000000000000cf9a12158ed9a6ebb60, 0000000000000000000000000000000000000000000000000000000000000001, 0000000000000000000000000000000000000000000000000000000fc50d6d88, 0000000000000000000000000000000000000000000000000000000000000001, 0000000000000000000000000000000000000000000000000000000f9bc85881, 0000000000000000000000000000000000000000000000000000000000000001, 000000000000000000000000000000000000000000000e3013c87ac943e00000, 0000000000000000000000000000000000000000000000000000000000000001, 0000000000000000000000000000000000000000000007d2d9713bd0f291045f, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000fc50d6d88, 0000000000000000000000000000000000000000000000000000000000000000, 000000000000000000000000000000000000000000000000000000081fd8025e, 000000000000000000000000cd81398895bea7ad9eff273aeffc41a9d83b4dad )
125 CanonicalOrders.LogCanonicalOrderFilled( orderHash=994DCF63504767C97EBDF4214090B866956B75CD61C9709C91BD4C3A04FC931D, orderMaker=0xf6054488...246D2beb4, fillAmount=200000000000000000000, triggerPrice=0, orderFlags=DA05EEB7DE3841D10AF99D63F02542A91C958C36362DFD50829DCF35500C2910, fill=[{name:price, type:uint256, order:1, indexed:false, value:1009500, valueString:1009500}, {name:fee, type:uint128, order:2, indexed:false, value:0, valueString:0}, {name:isNegativeFee, type:bool, order:3, indexed:false, value:false, valueString:False}] )
126 SoloMargin.0x54d4cc60cf7d570631cc1a58942812cb0fc461713613400f56932040c3497d19( 0x54d4cc60cf7d570631cc1a58942812cb0fc461713613400f56932040c3497d19, 0x000000000000000000000000f809e07870dca762b9536d61a4fbef1a17178092, 0x000000000000000000000000f6054488c194d5fc9cc16c3e3ce2fba246d2beb4, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000003, 0000000000000000000000000000000000000000000000000000000000000002, 0000000000000000000000000000000000000000000000000000000000000001, 00000000000000000000000000000000000000000000000ad78ebc5ac6200000, 0000000000000000000000000000000000000000000000000000000000000000, 000000000000000000000000000000000000000000000cef297760058143cfd6, 0000000000000000000000000000000000000000000000000000000000000000, 000000000000000000000000000000000000000000000000000000000c08bfe0, 0000000000000000000000000000000000000000000000000000000000000001, 0000000000000000000000000000000000000000000000000000000f903f8dd7, 0000000000000000000000000000000000000000000000000000000000000000, 00000000000000000000000000000000000000000000000ad78ebc5ac6200000, 0000000000000000000000000000000000000000000000000000000000000001, 000000000000000000000000000000000000000000000076a53150fa1b52e5d1, 0000000000000000000000000000000000000000000000000000000000000001, 000000000000000000000000000000000000000000000000000000000c08bfe0, 0000000000000000000000000000000000000000000000000000000000000001, 000000000000000000000000000000000000000000000000000000014749ea84, 000000000000000000000000cd81398895bea7ad9eff273aeffc41a9d83b4dad )
127 CanonicalOrders.LogCanonicalOrderFilled( orderHash=DA3487A68DFDE5E01AC5597638EDB8DA26D197A3671B6D53F3FC13B4241723CF, orderMaker=0x1b7835d2...1dbf296D1, fillAmount=9917000000000000786432, triggerPrice=0, orderFlags=47444E144EC180C2E8D71C7852DABFC5C52C04F09427AF1C660631B780306CA0, fill=[{name:price, type:uint256, order:1, indexed:false, value:1009600, valueString:1009600}, {name:fee, type:uint128, order:2, indexed:false, value:0, valueString:0}, {name:isNegativeFee, type:bool, order:3, indexed:false, value:false, valueString:False}] )
128 SoloMargin.0x54d4cc60cf7d570631cc1a58942812cb0fc461713613400f56932040c3497d19( 0x54d4cc60cf7d570631cc1a58942812cb0fc461713613400f56932040c3497d19, 0x000000000000000000000000f809e07870dca762b9536d61a4fbef1a17178092, 0x0000000000000000000000001b7835d2074914161dd6a2d48e393be1dbf296d1, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000003, 0000000000000000000000000000000000000000000000000000000000000002, 0000000000000000000000000000000000000000000000000000000000000001, 0000000000000000000000000000000000000000000002199a058d7b6ce00000, 0000000000000000000000000000000000000000000000000000000000000000, 000000000000000000000000000000000000000000000ae82254424e5999f41f, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000254c618c0, 0000000000000000000000000000000000000000000000000000000000000001, 0000000000000000000000000000000000000000000000000000000d5442e070, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000002199a058d7b6ce00000, 0000000000000000000000000000000000000000000000000000000000000000, 000000000000000000000000000000000000000000007791e8a351ba2e64b37a, 0000000000000000000000000000000000000000000000000000000000000001, 0000000000000000000000000000000000000000000000000000000254c618c0, 0000000000000000000000000000000000000000000000000000000000000001, 000000000000000000000000000000000000000000000000000000713b9c543d, 000000000000000000000000cd81398895bea7ad9eff273aeffc41a9d83b4dad )
129 CanonicalOrders.LogCanonicalOrderFilled( orderHash=23F15DFEA810D6EF891266CECB619F470F367A9F71546932EF94D1D4B2F55117, orderMaker=0x000f7f22...7cf107790, fillAmount=56882999999999999213568, triggerPrice=0, orderFlags=32C6E43775472ACD795C6015825EBE2640E295965D7D042CA5C8988C70D03690, fill=[{name:price, type:uint256, order:1, indexed:false, value:1010600, valueString:1010600}, {name:fee, type:uint128, order:2, indexed:false, value:0, valueString:0}, {name:isNegativeFee, type:bool, order:3, indexed:false, value:false, valueString:False}] )
130 SoloMargin.0x54d4cc60cf7d570631cc1a58942812cb0fc461713613400f56932040c3497d19( 0x54d4cc60cf7d570631cc1a58942812cb0fc461713613400f56932040c3497d19, 0x000000000000000000000000f809e07870dca762b9536d61a4fbef1a17178092, 0x000000000000000000000000000f7f22bfc28d940d4b68e13213ab17cf107790, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000003, 0000000000000000000000000000000000000000000000000000000000000002, 0000000000000000000000000000000000000000000000000000000000000001, 000000000000000000000000000000000000000000000c0ba23430f310e00000, 0000000000000000000000000000000000000000000000000000000000000001, 0000000000000000000000000000000000000000000000babbcb6fdcf0cd5cca, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000d626e2277, 0000000000000000000000000000000000000000000000000000000000000001, 00000000000000000000000000000000000000000000000000000000802588d9, 0000000000000000000000000000000000000000000000000000000000000000, 000000000000000000000000000000000000000000000c0ba23430f310e00000, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000001d8a664f8512baafacf, 0000000000000000000000000000000000000000000000000000000000000001, 0000000000000000000000000000000000000000000000000000000d626e2277, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000003112786ec8, 000000000000000000000000cd81398895bea7ad9eff273aeffc41a9d83b4dad )

Account State Difference:

  Address   Before After State Difference Code
0x1E0447b1...659614e4e
(dYdX: Solo Margin)
(Spark Pool)
24.335268514931365659 Eth24.356198602420710797 Eth0.020930087489345138
0xCd813988...9d83B4dAD
0xf809e078...a17178092
118.247272225090693286 Eth
Nonce: 204137
118.226342137601348148 Eth
Nonce: 204138
0.020930087489345138

Execution Trace

SoloMargin.operate( accounts=, actions= )
  • OperationImpl.bd76ecfd( )
    • DaiPriceOracle.getPrice( 0x6B175474E89094C44Da98b954EedeAC495271d0F ) => ( [{name:value, type:uint256, order:1, indexed:false, value:1011086519037487183, valueString:1011086519037487183}] )
    • UsdcPriceOracle.getPrice( 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 ) => ( [{name:value, type:uint256, order:1, indexed:false, value:1000000000000000000000000000000, valueString:1000000000000000000000000000000}] )
    • WethPriceOracle.getPrice( 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 ) => ( [{name:value, type:uint256, order:1, indexed:false, value:232880000000000000000, valueString:232880000000000000000}] )
      • Medianizer.STATICCALL( )
      • CanonicalOrders.getTradeCost( inputMarketId=3, outputMarketId=2, makerAccount=[{name:owner, type:address, order:1, indexed:false, value:0x66215D23B8A247C80c2D1B7beF4BefC2AB384bCE, valueString:0x66215D23B8A247C80c2D1B7beF4BefC2AB384bCE}, {name:number, type:uint256, order:2, indexed:false, value:0, valueString:0}], takerAccount=[{name:owner, type:address, order:1, indexed:false, value:0xf809e07870dca762B9536d61A4fBEF1a17178092, valueString:0xf809e07870dca762B9536d61A4fBEF1a17178092}, {name:number, type:uint256, order:2, indexed:false, value:0, valueString:0}], oldInputPar=[{name:sign, type:bool, order:1, indexed:false, value:false, valueString:False}, {name:value, type:uint128, order:2, indexed:false, value:28089642108264174090403, valueString:28089642108264174090403}], newInputPar=[{name:sign, type:bool, order:1, indexed:false, value:true, valueString:True}, {name:value, type:uint128, order:2, indexed:false, value:36946050005913729827935, valueString:36946050005913729827935}], inputWei=[{name:sign, type:bool, order:1, indexed:false, value:true, valueString:True}, {name:value, type:uint256, order:2, indexed:false, value:67000000000000000000000, valueString:67000000000000000000000}], data=0x19D47E444183D9BFE3EEE582546E300D3E8A18F70C84DC4C01EE133D848173D100000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000E3013C87AC943E0000000000000000000000000000000000000000000000000000000000000000F7ECC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001C6BF5263400000000000000000000000000066215D23B8A247C80C2D1B7BEF4BEFC2AB384BCE0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005EEBCE8B00000000000000000000000000000000000000000000000000000000000F6B1100000000000000000000000000000000000000000000000000019945CA26200000000000000000000000000000000000000000000000000000000000000000003AA24C9863EA34B36269DAF9FF36345CEBFAD73046CC404D687CF103BF5739C5255D576F6D7A4A67488BB2F3C1E92891C0655D83E486C401AAFDC125D3A71C981B00 ) => ( [{name:sign, type:bool, order:1, indexed:false, value:false, valueString:False}, {name:denomination, type:uint8, order:2, indexed:false, value:0, valueString:0}, {name:ref, type:uint8, order:3, indexed:false, value:0, valueString:0}, {name:value, type:uint256, order:4, indexed:false, value:67730501000, valueString:67730501000}] )
        • Null: 0x000...001.c7ff9a50( )
        • CanonicalOrders.getTradeCost( inputMarketId=3, outputMarketId=2, makerAccount=[{name:owner, type:address, order:1, indexed:false, value:0xf6054488c194D5Fc9CC16C3e3cE2fbA246D2beb4, valueString:0xf6054488c194D5Fc9CC16C3e3cE2fbA246D2beb4}, {name:number, type:uint256, order:2, indexed:false, value:0, valueString:0}], takerAccount=[{name:owner, type:address, order:1, indexed:false, value:0xf809e07870dca762B9536d61A4fBEF1a17178092, valueString:0xf809e07870dca762B9536d61A4fBEF1a17178092}, {name:number, type:uint256, order:2, indexed:false, value:0, valueString:0}], oldInputPar=[{name:sign, type:bool, order:1, indexed:false, value:true, valueString:True}, {name:value, type:uint128, order:2, indexed:false, value:2383558654670203438736, valueString:2383558654670203438736}], newInputPar=[{name:sign, type:bool, order:1, indexed:false, value:true, valueString:True}, {name:value, type:uint128, order:2, indexed:false, value:2188619185022974485969, valueString:2188619185022974485969}], inputWei=[{name:sign, type:bool, order:1, indexed:false, value:false, valueString:False}, {name:value, type:uint256, order:2, indexed:false, value:200000000000000000000, valueString:200000000000000000000}], data=0xDA05EEB7DE3841D10AF99D63F02542A91C958C36362DFD50829DCF35500C29100000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000AD78EBC5AC620000000000000000000000000000000000000000000000000000000000000000F675C00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011C37937E08000000000000000000000000000F6054488C194D5FC9CC16C3E3CE2FBA246D2BEB40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005F10B35100000000000000000000000000000000000000000000000000000000000F675C00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F3472A52C915DA0B11F33D57CCE0379F0AC83803E6F14700D3D888803F635144663DFD3CBEDDF9F507DA32852C30D8715821FA3A8F1EDFFF4ADD1EDFB006ABE01C01 ) => ( [{name:sign, type:bool, order:1, indexed:false, value:true, valueString:True}, {name:denomination, type:uint8, order:2, indexed:false, value:0, valueString:0}, {name:ref, type:uint8, order:3, indexed:false, value:0, valueString:0}, {name:value, type:uint256, order:4, indexed:false, value:201900000, valueString:201900000}] )
          • Null: 0x000...001.7c7259a8( )
          • CanonicalOrders.getTradeCost( inputMarketId=3, outputMarketId=2, makerAccount=[{name:owner, type:address, order:1, indexed:false, value:0x1b7835d2074914161dD6A2d48E393Be1dbf296D1, valueString:0x1b7835d2074914161dD6A2d48E393Be1dbf296D1}, {name:number, type:uint256, order:2, indexed:false, value:0, valueString:0}], takerAccount=[{name:owner, type:address, order:1, indexed:false, value:0xf809e07870dca762B9536d61A4fBEF1a17178092, valueString:0xf809e07870dca762B9536d61A4fBEF1a17178092}, {name:number, type:uint256, order:2, indexed:false, value:0, valueString:0}], oldInputPar=[{name:sign, type:bool, order:1, indexed:false, value:false, valueString:False}, {name:value, type:uint128, order:2, indexed:false, value:555078778222563563329473, valueString:555078778222563563329473}], newInputPar=[{name:sign, type:bool, order:1, indexed:false, value:false, valueString:False}, {name:value, type:uint128, order:2, indexed:false, value:564653152684273752650618, valueString:564653152684273752650618}], inputWei=[{name:sign, type:bool, order:1, indexed:false, value:false, valueString:False}, {name:value, type:uint256, order:2, indexed:false, value:9917000000000000786432, valueString:9917000000000000786432}], data=0x47444E144EC180C2E8D71C7852DABFC5C52C04F09427AF1C660631B780306CA0000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000002199A058D7B6CE0000000000000000000000000000000000000000000000000000000000000000F67C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001B7835D2074914161DD6A2D48E393BE1DBF296D10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005EEBCDEE00000000000000000000000000000000000000000000000000000000000F67C000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000E2E1FE573AE110162DA8241BA2998212F44B8ADA231FA7B8A08647D07F8034A95D841EEDF253F2290295CAD39EBC852382DECF9BAF3C0BCF33FED4D0229829271B01 ) => ( [{name:sign, type:bool, order:1, indexed:false, value:true, valueString:True}, {name:denomination, type:uint8, order:2, indexed:false, value:0, valueString:0}, {name:ref, type:uint8, order:3, indexed:false, value:0, valueString:0}, {name:value, type:uint256, order:4, indexed:false, value:10012203200, valueString:10012203200}] )
            • Null: 0x000...001.ea6c1a06( )
            • CanonicalOrders.getTradeCost( inputMarketId=3, outputMarketId=2, makerAccount=[{name:owner, type:address, order:1, indexed:false, value:0x000f7f22bfC28D940d4B68e13213aB17cf107790, valueString:0x000f7f22bfC28D940d4B68e13213aB17cf107790}, {name:number, type:uint256, order:2, indexed:false, value:0, valueString:0}], takerAccount=[{name:owner, type:address, order:1, indexed:false, value:0xf809e07870dca762B9536d61A4fBEF1a17178092, valueString:0xf809e07870dca762B9536d61A4fBEF1a17178092}, {name:number, type:uint256, order:2, indexed:false, value:0, valueString:0}], oldInputPar=[{name:sign, type:bool, order:1, indexed:false, value:true, valueString:True}, {name:value, type:uint128, order:2, indexed:false, value:46641350740954166234236, valueString:46641350740954166234236}], newInputPar=[{name:sign, type:bool, order:1, indexed:false, value:false, valueString:False}, {name:value, type:uint128, order:2, indexed:false, value:8718853183926384130767, valueString:8718853183926384130767}], inputWei=[{name:sign, type:bool, order:1, indexed:false, value:false, valueString:False}, {name:value, type:uint256, order:2, indexed:false, value:56882999999999999213568, valueString:56882999999999999213568}], data=0x32C6E43775472ACD795C6015825EBE2640E295965D7D042CA5C8988C70D036900000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000152D02C7E14AF600000000000000000000000000000000000000000000000000000000000000000F6BA8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000038D7EA4C68000000000000000000000000000000F7F22BFC28D940D4B68E13213AB17CF1077900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005EEBD3F700000000000000000000000000000000000000000000000000000000000F6BA800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000AC14801EFA9351EF5A0985B5111B6598989B958C41FBA74CFE49ED6BE8F53F2D1CAB631FB7D9E75154C7C22242A17CBEFA46B867A6C50964C3866B02B5908F611C01 ) => ( [{name:sign, type:bool, order:1, indexed:false, value:true, valueString:True}, {name:denomination, type:uint8, order:2, indexed:false, value:0, valueString:0}, {name:ref, type:uint8, order:3, indexed:false, value:0, valueString:0}, {name:value, type:uint256, order:4, indexed:false, value:57485959799, valueString:57485959799}] )
              • Null: 0x000...001.a356f597( )
                operate[Operation (ln:5600)]
                File 1 of 7: SoloMargin
                /*
                
                    Copyright 2019 dYdX Trading Inc.
                
                    Licensed under the Apache License, Version 2.0 (the "License");
                    you may not use this file except in compliance with the License.
                    You may obtain a copy of the License at
                
                    http://www.apache.org/licenses/LICENSE-2.0
                
                    Unless required by applicable law or agreed to in writing, software
                    distributed under the License is distributed on an "AS IS" BASIS,
                    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                    See the License for the specific language governing permissions and
                    limitations under the License.
                
                */
                
                pragma solidity 0.5.7;
                pragma experimental ABIEncoderV2;
                
                // File: openzeppelin-solidity/contracts/ownership/Ownable.sol
                
                /**
                 * @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 private _owner;
                
                    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
                
                    /**
                     * @dev The Ownable constructor sets the original `owner` of the contract to the sender
                     * account.
                     */
                    constructor () internal {
                        _owner = msg.sender;
                        emit OwnershipTransferred(address(0), _owner);
                    }
                
                    /**
                     * @return the address of the owner.
                     */
                    function owner() public view returns (address) {
                        return _owner;
                    }
                
                    /**
                     * @dev Throws if called by any account other than the owner.
                     */
                    modifier onlyOwner() {
                        require(isOwner());
                        _;
                    }
                
                    /**
                     * @return true if `msg.sender` is the owner of the contract.
                     */
                    function isOwner() public view returns (bool) {
                        return msg.sender == _owner;
                    }
                
                    /**
                     * @dev Allows the current owner to relinquish control of the contract.
                     * @notice Renouncing to ownership will leave the contract without an owner.
                     * It will not be possible to call the functions with the `onlyOwner`
                     * modifier anymore.
                     */
                    function renounceOwnership() public onlyOwner {
                        emit OwnershipTransferred(_owner, address(0));
                        _owner = address(0);
                    }
                
                    /**
                     * @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 {
                        _transferOwnership(newOwner);
                    }
                
                    /**
                     * @dev Transfers control of the contract to a newOwner.
                     * @param newOwner The address to transfer ownership to.
                     */
                    function _transferOwnership(address newOwner) internal {
                        require(newOwner != address(0));
                        emit OwnershipTransferred(_owner, newOwner);
                        _owner = newOwner;
                    }
                }
                
                // File: openzeppelin-solidity/contracts/utils/ReentrancyGuard.sol
                
                /**
                 * @title Helps contracts guard against reentrancy attacks.
                 * @author Remco Bloemen <remco@2π.com>, Eenae <alexey@mixbytes.io>
                 * @dev If you mark a function `nonReentrant`, you should also
                 * mark it `external`.
                 */
                contract ReentrancyGuard {
                    /// @dev counter to allow mutex lock with only one SSTORE operation
                    uint256 private _guardCounter;
                
                    constructor () internal {
                        // The counter starts at one to prevent changing it from zero to a non-zero
                        // value, which is a more expensive operation.
                        _guardCounter = 1;
                    }
                
                    /**
                     * @dev Prevents a contract from calling itself, directly or indirectly.
                     * Calling a `nonReentrant` function from another `nonReentrant`
                     * function is not supported. It is possible to prevent this from happening
                     * by making the `nonReentrant` function external, and make it call a
                     * `private` function that does the actual work.
                     */
                    modifier nonReentrant() {
                        _guardCounter += 1;
                        uint256 localCounter = _guardCounter;
                        _;
                        require(localCounter == _guardCounter);
                    }
                }
                
                // File: openzeppelin-solidity/contracts/math/SafeMath.sol
                
                /**
                 * @title SafeMath
                 * @dev Unsigned math operations with safety checks that revert on error
                 */
                library SafeMath {
                    /**
                    * @dev Multiplies two unsigned integers, reverts on overflow.
                    */
                    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
                        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
                        // benefit is lost if 'b' is also tested.
                        // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
                        if (a == 0) {
                            return 0;
                        }
                
                        uint256 c = a * b;
                        require(c / a == b);
                
                        return c;
                    }
                
                    /**
                    * @dev Integer division of two unsigned integers truncating the quotient, reverts on division by zero.
                    */
                    function div(uint256 a, uint256 b) internal pure returns (uint256) {
                        // Solidity only automatically asserts when dividing by 0
                        require(b > 0);
                        uint256 c = a / b;
                        // assert(a == b * c + a % b); // There is no case in which this doesn't hold
                
                        return c;
                    }
                
                    /**
                    * @dev Subtracts two unsigned integers, reverts on overflow (i.e. if subtrahend is greater than minuend).
                    */
                    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                        require(b <= a);
                        uint256 c = a - b;
                
                        return c;
                    }
                
                    /**
                    * @dev Adds two unsigned integers, reverts on overflow.
                    */
                    function add(uint256 a, uint256 b) internal pure returns (uint256) {
                        uint256 c = a + b;
                        require(c >= a);
                
                        return c;
                    }
                
                    /**
                    * @dev Divides two unsigned integers and returns the remainder (unsigned integer modulo),
                    * reverts when dividing by zero.
                    */
                    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
                        require(b != 0);
                        return a % b;
                    }
                }
                
                // File: contracts/protocol/lib/Require.sol
                
                /**
                 * @title Require
                 * @author dYdX
                 *
                 * Stringifies parameters to pretty-print revert messages. Costs more gas than regular require()
                 */
                library Require {
                
                    // ============ Constants ============
                
                    uint256 constant ASCII_ZERO = 48; // '0'
                    uint256 constant ASCII_RELATIVE_ZERO = 87; // 'a' - 10
                    uint256 constant ASCII_LOWER_EX = 120; // 'x'
                    bytes2 constant COLON = 0x3a20; // ': '
                    bytes2 constant COMMA = 0x2c20; // ', '
                    bytes2 constant LPAREN = 0x203c; // ' <'
                    byte constant RPAREN = 0x3e; // '>'
                    uint256 constant FOUR_BIT_MASK = 0xf;
                
                    // ============ Library Functions ============
                
                    function that(
                        bool must,
                        bytes32 file,
                        bytes32 reason
                    )
                        internal
                        pure
                    {
                        if (!must) {
                            revert(
                                string(
                                    abi.encodePacked(
                                        stringify(file),
                                        COLON,
                                        stringify(reason)
                                    )
                                )
                            );
                        }
                    }
                
                    function that(
                        bool must,
                        bytes32 file,
                        bytes32 reason,
                        uint256 payloadA
                    )
                        internal
                        pure
                    {
                        if (!must) {
                            revert(
                                string(
                                    abi.encodePacked(
                                        stringify(file),
                                        COLON,
                                        stringify(reason),
                                        LPAREN,
                                        stringify(payloadA),
                                        RPAREN
                                    )
                                )
                            );
                        }
                    }
                
                    function that(
                        bool must,
                        bytes32 file,
                        bytes32 reason,
                        uint256 payloadA,
                        uint256 payloadB
                    )
                        internal
                        pure
                    {
                        if (!must) {
                            revert(
                                string(
                                    abi.encodePacked(
                                        stringify(file),
                                        COLON,
                                        stringify(reason),
                                        LPAREN,
                                        stringify(payloadA),
                                        COMMA,
                                        stringify(payloadB),
                                        RPAREN
                                    )
                                )
                            );
                        }
                    }
                
                    function that(
                        bool must,
                        bytes32 file,
                        bytes32 reason,
                        address payloadA
                    )
                        internal
                        pure
                    {
                        if (!must) {
                            revert(
                                string(
                                    abi.encodePacked(
                                        stringify(file),
                                        COLON,
                                        stringify(reason),
                                        LPAREN,
                                        stringify(payloadA),
                                        RPAREN
                                    )
                                )
                            );
                        }
                    }
                
                    function that(
                        bool must,
                        bytes32 file,
                        bytes32 reason,
                        address payloadA,
                        uint256 payloadB
                    )
                        internal
                        pure
                    {
                        if (!must) {
                            revert(
                                string(
                                    abi.encodePacked(
                                        stringify(file),
                                        COLON,
                                        stringify(reason),
                                        LPAREN,
                                        stringify(payloadA),
                                        COMMA,
                                        stringify(payloadB),
                                        RPAREN
                                    )
                                )
                            );
                        }
                    }
                
                    function that(
                        bool must,
                        bytes32 file,
                        bytes32 reason,
                        address payloadA,
                        uint256 payloadB,
                        uint256 payloadC
                    )
                        internal
                        pure
                    {
                        if (!must) {
                            revert(
                                string(
                                    abi.encodePacked(
                                        stringify(file),
                                        COLON,
                                        stringify(reason),
                                        LPAREN,
                                        stringify(payloadA),
                                        COMMA,
                                        stringify(payloadB),
                                        COMMA,
                                        stringify(payloadC),
                                        RPAREN
                                    )
                                )
                            );
                        }
                    }
                
                    // ============ Private Functions ============
                
                    function stringify(
                        bytes32 input
                    )
                        private
                        pure
                        returns (bytes memory)
                    {
                        // put the input bytes into the result
                        bytes memory result = abi.encodePacked(input);
                
                        // determine the length of the input by finding the location of the last non-zero byte
                        for (uint256 i = 32; i > 0; ) {
                            // reverse-for-loops with unsigned integer
                            /* solium-disable-next-line security/no-modify-for-iter-var */
                            i--;
                
                            // find the last non-zero byte in order to determine the length
                            if (result[i] != 0) {
                                uint256 length = i + 1;
                
                                /* solium-disable-next-line security/no-inline-assembly */
                                assembly {
                                    mstore(result, length) // r.length = length;
                                }
                
                                return result;
                            }
                        }
                
                        // all bytes are zero
                        return new bytes(0);
                    }
                
                    function stringify(
                        uint256 input
                    )
                        private
                        pure
                        returns (bytes memory)
                    {
                        if (input == 0) {
                            return "0";
                        }
                
                        // get the final string length
                        uint256 j = input;
                        uint256 length;
                        while (j != 0) {
                            length++;
                            j /= 10;
                        }
                
                        // allocate the string
                        bytes memory bstr = new bytes(length);
                
                        // populate the string starting with the least-significant character
                        j = input;
                        for (uint256 i = length; i > 0; ) {
                            // reverse-for-loops with unsigned integer
                            /* solium-disable-next-line security/no-modify-for-iter-var */
                            i--;
                
                            // take last decimal digit
                            bstr[i] = byte(uint8(ASCII_ZERO + (j % 10)));
                
                            // remove the last decimal digit
                            j /= 10;
                        }
                
                        return bstr;
                    }
                
                    function stringify(
                        address input
                    )
                        private
                        pure
                        returns (bytes memory)
                    {
                        uint256 z = uint256(input);
                
                        // addresses are "0x" followed by 20 bytes of data which take up 2 characters each
                        bytes memory result = new bytes(42);
                
                        // populate the result with "0x"
                        result[0] = byte(uint8(ASCII_ZERO));
                        result[1] = byte(uint8(ASCII_LOWER_EX));
                
                        // for each byte (starting from the lowest byte), populate the result with two characters
                        for (uint256 i = 0; i < 20; i++) {
                            // each byte takes two characters
                            uint256 shift = i * 2;
                
                            // populate the least-significant character
                            result[41 - shift] = char(z & FOUR_BIT_MASK);
                            z = z >> 4;
                
                            // populate the most-significant character
                            result[40 - shift] = char(z & FOUR_BIT_MASK);
                            z = z >> 4;
                        }
                
                        return result;
                    }
                
                    function char(
                        uint256 input
                    )
                        private
                        pure
                        returns (byte)
                    {
                        // return ASCII digit (0-9)
                        if (input < 10) {
                            return byte(uint8(input + ASCII_ZERO));
                        }
                
                        // return ASCII letter (a-f)
                        return byte(uint8(input + ASCII_RELATIVE_ZERO));
                    }
                }
                
                // File: contracts/protocol/lib/Math.sol
                
                /**
                 * @title Math
                 * @author dYdX
                 *
                 * Library for non-standard Math functions
                 */
                library Math {
                    using SafeMath for uint256;
                
                    // ============ Constants ============
                
                    bytes32 constant FILE = "Math";
                
                    // ============ Library Functions ============
                
                    /*
                     * Return target * (numerator / denominator).
                     */
                    function getPartial(
                        uint256 target,
                        uint256 numerator,
                        uint256 denominator
                    )
                        internal
                        pure
                        returns (uint256)
                    {
                        return target.mul(numerator).div(denominator);
                    }
                
                    /*
                     * Return target * (numerator / denominator), but rounded up.
                     */
                    function getPartialRoundUp(
                        uint256 target,
                        uint256 numerator,
                        uint256 denominator
                    )
                        internal
                        pure
                        returns (uint256)
                    {
                        if (target == 0 || numerator == 0) {
                            // SafeMath will check for zero denominator
                            return SafeMath.div(0, denominator);
                        }
                        return target.mul(numerator).sub(1).div(denominator).add(1);
                    }
                
                    function to128(
                        uint256 number
                    )
                        internal
                        pure
                        returns (uint128)
                    {
                        uint128 result = uint128(number);
                        Require.that(
                            result == number,
                            FILE,
                            "Unsafe cast to uint128"
                        );
                        return result;
                    }
                
                    function to96(
                        uint256 number
                    )
                        internal
                        pure
                        returns (uint96)
                    {
                        uint96 result = uint96(number);
                        Require.that(
                            result == number,
                            FILE,
                            "Unsafe cast to uint96"
                        );
                        return result;
                    }
                
                    function to32(
                        uint256 number
                    )
                        internal
                        pure
                        returns (uint32)
                    {
                        uint32 result = uint32(number);
                        Require.that(
                            result == number,
                            FILE,
                            "Unsafe cast to uint32"
                        );
                        return result;
                    }
                
                    function min(
                        uint256 a,
                        uint256 b
                    )
                        internal
                        pure
                        returns (uint256)
                    {
                        return a < b ? a : b;
                    }
                
                    function max(
                        uint256 a,
                        uint256 b
                    )
                        internal
                        pure
                        returns (uint256)
                    {
                        return a > b ? a : b;
                    }
                }
                
                // File: contracts/protocol/lib/Types.sol
                
                /**
                 * @title Types
                 * @author dYdX
                 *
                 * Library for interacting with the basic structs used in Solo
                 */
                library Types {
                    using Math for uint256;
                
                    // ============ AssetAmount ============
                
                    enum AssetDenomination {
                        Wei, // the amount is denominated in wei
                        Par  // the amount is denominated in par
                    }
                
                    enum AssetReference {
                        Delta, // the amount is given as a delta from the current value
                        Target // the amount is given as an exact number to end up at
                    }
                
                    struct AssetAmount {
                        bool sign; // true if positive
                        AssetDenomination denomination;
                        AssetReference ref;
                        uint256 value;
                    }
                
                    // ============ Par (Principal Amount) ============
                
                    // Total borrow and supply values for a market
                    struct TotalPar {
                        uint128 borrow;
                        uint128 supply;
                    }
                
                    // Individual principal amount for an account
                    struct Par {
                        bool sign; // true if positive
                        uint128 value;
                    }
                
                    function zeroPar()
                        internal
                        pure
                        returns (Par memory)
                    {
                        return Par({
                            sign: false,
                            value: 0
                        });
                    }
                
                    function sub(
                        Par memory a,
                        Par memory b
                    )
                        internal
                        pure
                        returns (Par memory)
                    {
                        return add(a, negative(b));
                    }
                
                    function add(
                        Par memory a,
                        Par memory b
                    )
                        internal
                        pure
                        returns (Par memory)
                    {
                        Par memory result;
                        if (a.sign == b.sign) {
                            result.sign = a.sign;
                            result.value = SafeMath.add(a.value, b.value).to128();
                        } else {
                            if (a.value >= b.value) {
                                result.sign = a.sign;
                                result.value = SafeMath.sub(a.value, b.value).to128();
                            } else {
                                result.sign = b.sign;
                                result.value = SafeMath.sub(b.value, a.value).to128();
                            }
                        }
                        return result;
                    }
                
                    function equals(
                        Par memory a,
                        Par memory b
                    )
                        internal
                        pure
                        returns (bool)
                    {
                        if (a.value == b.value) {
                            if (a.value == 0) {
                                return true;
                            }
                            return a.sign == b.sign;
                        }
                        return false;
                    }
                
                    function negative(
                        Par memory a
                    )
                        internal
                        pure
                        returns (Par memory)
                    {
                        return Par({
                            sign: !a.sign,
                            value: a.value
                        });
                    }
                
                    function isNegative(
                        Par memory a
                    )
                        internal
                        pure
                        returns (bool)
                    {
                        return !a.sign && a.value > 0;
                    }
                
                    function isPositive(
                        Par memory a
                    )
                        internal
                        pure
                        returns (bool)
                    {
                        return a.sign && a.value > 0;
                    }
                
                    function isZero(
                        Par memory a
                    )
                        internal
                        pure
                        returns (bool)
                    {
                        return a.value == 0;
                    }
                
                    // ============ Wei (Token Amount) ============
                
                    // Individual token amount for an account
                    struct Wei {
                        bool sign; // true if positive
                        uint256 value;
                    }
                
                    function zeroWei()
                        internal
                        pure
                        returns (Wei memory)
                    {
                        return Wei({
                            sign: false,
                            value: 0
                        });
                    }
                
                    function sub(
                        Wei memory a,
                        Wei memory b
                    )
                        internal
                        pure
                        returns (Wei memory)
                    {
                        return add(a, negative(b));
                    }
                
                    function add(
                        Wei memory a,
                        Wei memory b
                    )
                        internal
                        pure
                        returns (Wei memory)
                    {
                        Wei memory result;
                        if (a.sign == b.sign) {
                            result.sign = a.sign;
                            result.value = SafeMath.add(a.value, b.value);
                        } else {
                            if (a.value >= b.value) {
                                result.sign = a.sign;
                                result.value = SafeMath.sub(a.value, b.value);
                            } else {
                                result.sign = b.sign;
                                result.value = SafeMath.sub(b.value, a.value);
                            }
                        }
                        return result;
                    }
                
                    function equals(
                        Wei memory a,
                        Wei memory b
                    )
                        internal
                        pure
                        returns (bool)
                    {
                        if (a.value == b.value) {
                            if (a.value == 0) {
                                return true;
                            }
                            return a.sign == b.sign;
                        }
                        return false;
                    }
                
                    function negative(
                        Wei memory a
                    )
                        internal
                        pure
                        returns (Wei memory)
                    {
                        return Wei({
                            sign: !a.sign,
                            value: a.value
                        });
                    }
                
                    function isNegative(
                        Wei memory a
                    )
                        internal
                        pure
                        returns (bool)
                    {
                        return !a.sign && a.value > 0;
                    }
                
                    function isPositive(
                        Wei memory a
                    )
                        internal
                        pure
                        returns (bool)
                    {
                        return a.sign && a.value > 0;
                    }
                
                    function isZero(
                        Wei memory a
                    )
                        internal
                        pure
                        returns (bool)
                    {
                        return a.value == 0;
                    }
                }
                
                // File: contracts/protocol/lib/Account.sol
                
                /**
                 * @title Account
                 * @author dYdX
                 *
                 * Library of structs and functions that represent an account
                 */
                library Account {
                    // ============ Enums ============
                
                    /*
                     * Most-recently-cached account status.
                     *
                     * Normal: Can only be liquidated if the account values are violating the global margin-ratio.
                     * Liquid: Can be liquidated no matter the account values.
                     *         Can be vaporized if there are no more positive account values.
                     * Vapor:  Has only negative (or zeroed) account values. Can be vaporized.
                     *
                     */
                    enum Status {
                        Normal,
                        Liquid,
                        Vapor
                    }
                
                    // ============ Structs ============
                
                    // Represents the unique key that specifies an account
                    struct Info {
                        address owner;  // The address that owns the account
                        uint256 number; // A nonce that allows a single address to control many accounts
                    }
                
                    // The complete storage for any account
                    struct Storage {
                        mapping (uint256 => Types.Par) balances; // Mapping from marketId to principal
                        Status status;
                    }
                
                    // ============ Library Functions ============
                
                    function equals(
                        Info memory a,
                        Info memory b
                    )
                        internal
                        pure
                        returns (bool)
                    {
                        return a.owner == b.owner && a.number == b.number;
                    }
                }
                
                // File: contracts/protocol/lib/Monetary.sol
                
                /**
                 * @title Monetary
                 * @author dYdX
                 *
                 * Library for types involving money
                 */
                library Monetary {
                
                    /*
                     * The price of a base-unit of an asset.
                     */
                    struct Price {
                        uint256 value;
                    }
                
                    /*
                     * Total value of an some amount of an asset. Equal to (price * amount).
                     */
                    struct Value {
                        uint256 value;
                    }
                }
                
                // File: contracts/protocol/lib/Cache.sol
                
                /**
                 * @title Cache
                 * @author dYdX
                 *
                 * Library for caching information about markets
                 */
                library Cache {
                    using Cache for MarketCache;
                    using Storage for Storage.State;
                
                    // ============ Structs ============
                
                    struct MarketInfo {
                        bool isClosing;
                        uint128 borrowPar;
                        Monetary.Price price;
                    }
                
                    struct MarketCache {
                        MarketInfo[] markets;
                    }
                
                    // ============ Setter Functions ============
                
                    /**
                     * Initialize an empty cache for some given number of total markets.
                     */
                    function create(
                        uint256 numMarkets
                    )
                        internal
                        pure
                        returns (MarketCache memory)
                    {
                        return MarketCache({
                            markets: new MarketInfo[](numMarkets)
                        });
                    }
                
                    /**
                     * Add market information (price and total borrowed par if the market is closing) to the cache.
                     * Return true if the market information did not previously exist in the cache.
                     */
                    function addMarket(
                        MarketCache memory cache,
                        Storage.State storage state,
                        uint256 marketId
                    )
                        internal
                        view
                        returns (bool)
                    {
                        if (cache.hasMarket(marketId)) {
                            return false;
                        }
                        cache.markets[marketId].price = state.fetchPrice(marketId);
                        if (state.markets[marketId].isClosing) {
                            cache.markets[marketId].isClosing = true;
                            cache.markets[marketId].borrowPar = state.getTotalPar(marketId).borrow;
                        }
                        return true;
                    }
                
                    // ============ Getter Functions ============
                
                    function getNumMarkets(
                        MarketCache memory cache
                    )
                        internal
                        pure
                        returns (uint256)
                    {
                        return cache.markets.length;
                    }
                
                    function hasMarket(
                        MarketCache memory cache,
                        uint256 marketId
                    )
                        internal
                        pure
                        returns (bool)
                    {
                        return cache.markets[marketId].price.value != 0;
                    }
                
                    function getIsClosing(
                        MarketCache memory cache,
                        uint256 marketId
                    )
                        internal
                        pure
                        returns (bool)
                    {
                        return cache.markets[marketId].isClosing;
                    }
                
                    function getPrice(
                        MarketCache memory cache,
                        uint256 marketId
                    )
                        internal
                        pure
                        returns (Monetary.Price memory)
                    {
                        return cache.markets[marketId].price;
                    }
                
                    function getBorrowPar(
                        MarketCache memory cache,
                        uint256 marketId
                    )
                        internal
                        pure
                        returns (uint128)
                    {
                        return cache.markets[marketId].borrowPar;
                    }
                }
                
                // File: contracts/protocol/lib/Decimal.sol
                
                /**
                 * @title Decimal
                 * @author dYdX
                 *
                 * Library that defines a fixed-point number with 18 decimal places.
                 */
                library Decimal {
                    using SafeMath for uint256;
                
                    // ============ Constants ============
                
                    uint256 constant BASE = 10**18;
                
                    // ============ Structs ============
                
                    struct D256 {
                        uint256 value;
                    }
                
                    // ============ Functions ============
                
                    function one()
                        internal
                        pure
                        returns (D256 memory)
                    {
                        return D256({ value: BASE });
                    }
                
                    function onePlus(
                        D256 memory d
                    )
                        internal
                        pure
                        returns (D256 memory)
                    {
                        return D256({ value: d.value.add(BASE) });
                    }
                
                    function mul(
                        uint256 target,
                        D256 memory d
                    )
                        internal
                        pure
                        returns (uint256)
                    {
                        return Math.getPartial(target, d.value, BASE);
                    }
                
                    function div(
                        uint256 target,
                        D256 memory d
                    )
                        internal
                        pure
                        returns (uint256)
                    {
                        return Math.getPartial(target, BASE, d.value);
                    }
                }
                
                // File: contracts/protocol/lib/Time.sol
                
                /**
                 * @title Time
                 * @author dYdX
                 *
                 * Library for dealing with time, assuming timestamps fit within 32 bits (valid until year 2106)
                 */
                library Time {
                
                    // ============ Library Functions ============
                
                    function currentTime()
                        internal
                        view
                        returns (uint32)
                    {
                        return Math.to32(block.timestamp);
                    }
                }
                
                // File: contracts/protocol/lib/Interest.sol
                
                /**
                 * @title Interest
                 * @author dYdX
                 *
                 * Library for managing the interest rate and interest indexes of Solo
                 */
                library Interest {
                    using Math for uint256;
                    using SafeMath for uint256;
                
                    // ============ Constants ============
                
                    bytes32 constant FILE = "Interest";
                    uint64 constant BASE = 10**18;
                
                    // ============ Structs ============
                
                    struct Rate {
                        uint256 value;
                    }
                
                    struct Index {
                        uint96 borrow;
                        uint96 supply;
                        uint32 lastUpdate;
                    }
                
                    // ============ Library Functions ============
                
                    /**
                     * Get a new market Index based on the old index and market interest rate.
                     * Calculate interest for borrowers by using the formula rate * time. Approximates
                     * continuously-compounded interest when called frequently, but is much more
                     * gas-efficient to calculate. For suppliers, the interest rate is adjusted by the earningsRate,
                     * then prorated the across all suppliers.
                     *
                     * @param  index         The old index for a market
                     * @param  rate          The current interest rate of the market
                     * @param  totalPar      The total supply and borrow par values of the market
                     * @param  earningsRate  The portion of the interest that is forwarded to the suppliers
                     * @return               The updated index for a market
                     */
                    function calculateNewIndex(
                        Index memory index,
                        Rate memory rate,
                        Types.TotalPar memory totalPar,
                        Decimal.D256 memory earningsRate
                    )
                        internal
                        view
                        returns (Index memory)
                    {
                        (
                            Types.Wei memory supplyWei,
                            Types.Wei memory borrowWei
                        ) = totalParToWei(totalPar, index);
                
                        // get interest increase for borrowers
                        uint32 currentTime = Time.currentTime();
                        uint256 borrowInterest = rate.value.mul(uint256(currentTime).sub(index.lastUpdate));
                
                        // get interest increase for suppliers
                        uint256 supplyInterest;
                        if (Types.isZero(supplyWei)) {
                            supplyInterest = 0;
                        } else {
                            supplyInterest = Decimal.mul(borrowInterest, earningsRate);
                            if (borrowWei.value < supplyWei.value) {
                                supplyInterest = Math.getPartial(supplyInterest, borrowWei.value, supplyWei.value);
                            }
                        }
                        assert(supplyInterest <= borrowInterest);
                
                        return Index({
                            borrow: Math.getPartial(index.borrow, borrowInterest, BASE).add(index.borrow).to96(),
                            supply: Math.getPartial(index.supply, supplyInterest, BASE).add(index.supply).to96(),
                            lastUpdate: currentTime
                        });
                    }
                
                    function newIndex()
                        internal
                        view
                        returns (Index memory)
                    {
                        return Index({
                            borrow: BASE,
                            supply: BASE,
                            lastUpdate: Time.currentTime()
                        });
                    }
                
                    /*
                     * Convert a principal amount to a token amount given an index.
                     */
                    function parToWei(
                        Types.Par memory input,
                        Index memory index
                    )
                        internal
                        pure
                        returns (Types.Wei memory)
                    {
                        uint256 inputValue = uint256(input.value);
                        if (input.sign) {
                            return Types.Wei({
                                sign: true,
                                value: inputValue.getPartial(index.supply, BASE)
                            });
                        } else {
                            return Types.Wei({
                                sign: false,
                                value: inputValue.getPartialRoundUp(index.borrow, BASE)
                            });
                        }
                    }
                
                    /*
                     * Convert a token amount to a principal amount given an index.
                     */
                    function weiToPar(
                        Types.Wei memory input,
                        Index memory index
                    )
                        internal
                        pure
                        returns (Types.Par memory)
                    {
                        if (input.sign) {
                            return Types.Par({
                                sign: true,
                                value: input.value.getPartial(BASE, index.supply).to128()
                            });
                        } else {
                            return Types.Par({
                                sign: false,
                                value: input.value.getPartialRoundUp(BASE, index.borrow).to128()
                            });
                        }
                    }
                
                    /*
                     * Convert the total supply and borrow principal amounts of a market to total supply and borrow
                     * token amounts.
                     */
                    function totalParToWei(
                        Types.TotalPar memory totalPar,
                        Index memory index
                    )
                        internal
                        pure
                        returns (Types.Wei memory, Types.Wei memory)
                    {
                        Types.Par memory supplyPar = Types.Par({
                            sign: true,
                            value: totalPar.supply
                        });
                        Types.Par memory borrowPar = Types.Par({
                            sign: false,
                            value: totalPar.borrow
                        });
                        Types.Wei memory supplyWei = parToWei(supplyPar, index);
                        Types.Wei memory borrowWei = parToWei(borrowPar, index);
                        return (supplyWei, borrowWei);
                    }
                }
                
                // File: contracts/protocol/interfaces/IErc20.sol
                
                /**
                 * @title IErc20
                 * @author dYdX
                 *
                 * Interface for using ERC20 Tokens. We have to use a special interface to call ERC20 functions so
                 * that we don't automatically revert when calling non-compliant tokens that have no return value for
                 * transfer(), transferFrom(), or approve().
                 */
                interface IErc20 {
                    event Transfer(
                        address indexed from,
                        address indexed to,
                        uint256 value
                    );
                
                    event Approval(
                        address indexed owner,
                        address indexed spender,
                        uint256 value
                    );
                
                    function totalSupply(
                    )
                        external
                        view
                        returns (uint256);
                
                    function balanceOf(
                        address who
                    )
                        external
                        view
                        returns (uint256);
                
                    function allowance(
                        address owner,
                        address spender
                    )
                        external
                        view
                        returns (uint256);
                
                    function transfer(
                        address to,
                        uint256 value
                    )
                        external;
                
                    function transferFrom(
                        address from,
                        address to,
                        uint256 value
                    )
                        external;
                
                    function approve(
                        address spender,
                        uint256 value
                    )
                        external;
                
                    function name()
                        external
                        view
                        returns (string memory);
                
                    function symbol()
                        external
                        view
                        returns (string memory);
                
                    function decimals()
                        external
                        view
                        returns (uint8);
                }
                
                // File: contracts/protocol/lib/Token.sol
                
                /**
                 * @title Token
                 * @author dYdX
                 *
                 * This library contains basic functions for interacting with ERC20 tokens. Modified to work with
                 * tokens that don't adhere strictly to the ERC20 standard (for example tokens that don't return a
                 * boolean value on success).
                 */
                library Token {
                
                    // ============ Constants ============
                
                    bytes32 constant FILE = "Token";
                
                    // ============ Library Functions ============
                
                    function balanceOf(
                        address token,
                        address owner
                    )
                        internal
                        view
                        returns (uint256)
                    {
                        return IErc20(token).balanceOf(owner);
                    }
                
                    function allowance(
                        address token,
                        address owner,
                        address spender
                    )
                        internal
                        view
                        returns (uint256)
                    {
                        return IErc20(token).allowance(owner, spender);
                    }
                
                    function approve(
                        address token,
                        address spender,
                        uint256 amount
                    )
                        internal
                    {
                        IErc20(token).approve(spender, amount);
                
                        Require.that(
                            checkSuccess(),
                            FILE,
                            "Approve failed"
                        );
                    }
                
                    function approveMax(
                        address token,
                        address spender
                    )
                        internal
                    {
                        approve(
                            token,
                            spender,
                            uint256(-1)
                        );
                    }
                
                    function transfer(
                        address token,
                        address to,
                        uint256 amount
                    )
                        internal
                    {
                        if (amount == 0 || to == address(this)) {
                            return;
                        }
                
                        IErc20(token).transfer(to, amount);
                
                        Require.that(
                            checkSuccess(),
                            FILE,
                            "Transfer failed"
                        );
                    }
                
                    function transferFrom(
                        address token,
                        address from,
                        address to,
                        uint256 amount
                    )
                        internal
                    {
                        if (amount == 0 || to == from) {
                            return;
                        }
                
                        IErc20(token).transferFrom(from, to, amount);
                
                        Require.that(
                            checkSuccess(),
                            FILE,
                            "TransferFrom failed"
                        );
                    }
                
                    // ============ Private Functions ============
                
                    /**
                     * Check the return value of the previous function up to 32 bytes. Return true if the previous
                     * function returned 0 bytes or 32 bytes that are not all-zero.
                     */
                    function checkSuccess(
                    )
                        private
                        pure
                        returns (bool)
                    {
                        uint256 returnValue = 0;
                
                        /* solium-disable-next-line security/no-inline-assembly */
                        assembly {
                            // check number of bytes returned from last function call
                            switch returndatasize
                
                            // no bytes returned: assume success
                            case 0x0 {
                                returnValue := 1
                            }
                
                            // 32 bytes returned: check if non-zero
                            case 0x20 {
                                // copy 32 bytes into scratch space
                                returndatacopy(0x0, 0x0, 0x20)
                
                                // load those bytes into returnValue
                                returnValue := mload(0x0)
                            }
                
                            // not sure what was returned: don't mark as success
                            default { }
                        }
                
                        return returnValue != 0;
                    }
                }
                
                // File: contracts/protocol/interfaces/IInterestSetter.sol
                
                /**
                 * @title IInterestSetter
                 * @author dYdX
                 *
                 * Interface that Interest Setters for Solo must implement in order to report interest rates.
                 */
                interface IInterestSetter {
                
                    // ============ Public Functions ============
                
                    /**
                     * Get the interest rate of a token given some borrowed and supplied amounts
                     *
                     * @param  token        The address of the ERC20 token for the market
                     * @param  borrowWei    The total borrowed token amount for the market
                     * @param  supplyWei    The total supplied token amount for the market
                     * @return              The interest rate per second
                     */
                    function getInterestRate(
                        address token,
                        uint256 borrowWei,
                        uint256 supplyWei
                    )
                        external
                        view
                        returns (Interest.Rate memory);
                }
                
                // File: contracts/protocol/interfaces/IPriceOracle.sol
                
                /**
                 * @title IPriceOracle
                 * @author dYdX
                 *
                 * Interface that Price Oracles for Solo must implement in order to report prices.
                 */
                contract IPriceOracle {
                
                    // ============ Constants ============
                
                    uint256 public constant ONE_DOLLAR = 10 ** 36;
                
                    // ============ Public Functions ============
                
                    /**
                     * Get the price of a token
                     *
                     * @param  token  The ERC20 token address of the market
                     * @return        The USD price of a base unit of the token, then multiplied by 10^36.
                     *                So a USD-stable coin with 18 decimal places would return 10^18.
                     *                This is the price of the base unit rather than the price of a "human-readable"
                     *                token amount. Every ERC20 may have a different number of decimals.
                     */
                    function getPrice(
                        address token
                    )
                        public
                        view
                        returns (Monetary.Price memory);
                }
                
                // File: contracts/protocol/lib/Storage.sol
                
                /**
                 * @title Storage
                 * @author dYdX
                 *
                 * Functions for reading, writing, and verifying state in Solo
                 */
                library Storage {
                    using Cache for Cache.MarketCache;
                    using Storage for Storage.State;
                    using Math for uint256;
                    using Types for Types.Par;
                    using Types for Types.Wei;
                    using SafeMath for uint256;
                
                    // ============ Constants ============
                
                    bytes32 constant FILE = "Storage";
                
                    // ============ Structs ============
                
                    // All information necessary for tracking a market
                    struct Market {
                        // Contract address of the associated ERC20 token
                        address token;
                
                        // Total aggregated supply and borrow amount of the entire market
                        Types.TotalPar totalPar;
                
                        // Interest index of the market
                        Interest.Index index;
                
                        // Contract address of the price oracle for this market
                        IPriceOracle priceOracle;
                
                        // Contract address of the interest setter for this market
                        IInterestSetter interestSetter;
                
                        // Multiplier on the marginRatio for this market
                        Decimal.D256 marginPremium;
                
                        // Multiplier on the liquidationSpread for this market
                        Decimal.D256 spreadPremium;
                
                        // Whether additional borrows are allowed for this market
                        bool isClosing;
                    }
                
                    // The global risk parameters that govern the health and security of the system
                    struct RiskParams {
                        // Required ratio of over-collateralization
                        Decimal.D256 marginRatio;
                
                        // Percentage penalty incurred by liquidated accounts
                        Decimal.D256 liquidationSpread;
                
                        // Percentage of the borrower's interest fee that gets passed to the suppliers
                        Decimal.D256 earningsRate;
                
                        // The minimum absolute borrow value of an account
                        // There must be sufficient incentivize to liquidate undercollateralized accounts
                        Monetary.Value minBorrowedValue;
                    }
                
                    // The maximum RiskParam values that can be set
                    struct RiskLimits {
                        uint64 marginRatioMax;
                        uint64 liquidationSpreadMax;
                        uint64 earningsRateMax;
                        uint64 marginPremiumMax;
                        uint64 spreadPremiumMax;
                        uint128 minBorrowedValueMax;
                    }
                
                    // The entire storage state of Solo
                    struct State {
                        // number of markets
                        uint256 numMarkets;
                
                        // marketId => Market
                        mapping (uint256 => Market) markets;
                
                        // owner => account number => Account
                        mapping (address => mapping (uint256 => Account.Storage)) accounts;
                
                        // Addresses that can control other users accounts
                        mapping (address => mapping (address => bool)) operators;
                
                        // Addresses that can control all users accounts
                        mapping (address => bool) globalOperators;
                
                        // mutable risk parameters of the system
                        RiskParams riskParams;
                
                        // immutable risk limits of the system
                        RiskLimits riskLimits;
                    }
                
                    // ============ Functions ============
                
                    function getToken(
                        Storage.State storage state,
                        uint256 marketId
                    )
                        internal
                        view
                        returns (address)
                    {
                        return state.markets[marketId].token;
                    }
                
                    function getTotalPar(
                        Storage.State storage state,
                        uint256 marketId
                    )
                        internal
                        view
                        returns (Types.TotalPar memory)
                    {
                        return state.markets[marketId].totalPar;
                    }
                
                    function getIndex(
                        Storage.State storage state,
                        uint256 marketId
                    )
                        internal
                        view
                        returns (Interest.Index memory)
                    {
                        return state.markets[marketId].index;
                    }
                
                    function getNumExcessTokens(
                        Storage.State storage state,
                        uint256 marketId
                    )
                        internal
                        view
                        returns (Types.Wei memory)
                    {
                        Interest.Index memory index = state.getIndex(marketId);
                        Types.TotalPar memory totalPar = state.getTotalPar(marketId);
                
                        address token = state.getToken(marketId);
                
                        Types.Wei memory balanceWei = Types.Wei({
                            sign: true,
                            value: Token.balanceOf(token, address(this))
                        });
                
                        (
                            Types.Wei memory supplyWei,
                            Types.Wei memory borrowWei
                        ) = Interest.totalParToWei(totalPar, index);
                
                        // borrowWei is negative, so subtracting it makes the value more positive
                        return balanceWei.sub(borrowWei).sub(supplyWei);
                    }
                
                    function getStatus(
                        Storage.State storage state,
                        Account.Info memory account
                    )
                        internal
                        view
                        returns (Account.Status)
                    {
                        return state.accounts[account.owner][account.number].status;
                    }
                
                    function getPar(
                        Storage.State storage state,
                        Account.Info memory account,
                        uint256 marketId
                    )
                        internal
                        view
                        returns (Types.Par memory)
                    {
                        return state.accounts[account.owner][account.number].balances[marketId];
                    }
                
                    function getWei(
                        Storage.State storage state,
                        Account.Info memory account,
                        uint256 marketId
                    )
                        internal
                        view
                        returns (Types.Wei memory)
                    {
                        Types.Par memory par = state.getPar(account, marketId);
                
                        if (par.isZero()) {
                            return Types.zeroWei();
                        }
                
                        Interest.Index memory index = state.getIndex(marketId);
                        return Interest.parToWei(par, index);
                    }
                
                    function getLiquidationSpreadForPair(
                        Storage.State storage state,
                        uint256 heldMarketId,
                        uint256 owedMarketId
                    )
                        internal
                        view
                        returns (Decimal.D256 memory)
                    {
                        uint256 result = state.riskParams.liquidationSpread.value;
                        result = Decimal.mul(result, Decimal.onePlus(state.markets[heldMarketId].spreadPremium));
                        result = Decimal.mul(result, Decimal.onePlus(state.markets[owedMarketId].spreadPremium));
                        return Decimal.D256({
                            value: result
                        });
                    }
                
                    function fetchNewIndex(
                        Storage.State storage state,
                        uint256 marketId,
                        Interest.Index memory index
                    )
                        internal
                        view
                        returns (Interest.Index memory)
                    {
                        Interest.Rate memory rate = state.fetchInterestRate(marketId, index);
                
                        return Interest.calculateNewIndex(
                            index,
                            rate,
                            state.getTotalPar(marketId),
                            state.riskParams.earningsRate
                        );
                    }
                
                    function fetchInterestRate(
                        Storage.State storage state,
                        uint256 marketId,
                        Interest.Index memory index
                    )
                        internal
                        view
                        returns (Interest.Rate memory)
                    {
                        Types.TotalPar memory totalPar = state.getTotalPar(marketId);
                        (
                            Types.Wei memory supplyWei,
                            Types.Wei memory borrowWei
                        ) = Interest.totalParToWei(totalPar, index);
                
                        Interest.Rate memory rate = state.markets[marketId].interestSetter.getInterestRate(
                            state.getToken(marketId),
                            borrowWei.value,
                            supplyWei.value
                        );
                
                        return rate;
                    }
                
                    function fetchPrice(
                        Storage.State storage state,
                        uint256 marketId
                    )
                        internal
                        view
                        returns (Monetary.Price memory)
                    {
                        IPriceOracle oracle = IPriceOracle(state.markets[marketId].priceOracle);
                        Monetary.Price memory price = oracle.getPrice(state.getToken(marketId));
                        Require.that(
                            price.value != 0,
                            FILE,
                            "Price cannot be zero",
                            marketId
                        );
                        return price;
                    }
                
                    function getAccountValues(
                        Storage.State storage state,
                        Account.Info memory account,
                        Cache.MarketCache memory cache,
                        bool adjustForLiquidity
                    )
                        internal
                        view
                        returns (Monetary.Value memory, Monetary.Value memory)
                    {
                        Monetary.Value memory supplyValue;
                        Monetary.Value memory borrowValue;
                
                        uint256 numMarkets = cache.getNumMarkets();
                        for (uint256 m = 0; m < numMarkets; m++) {
                            if (!cache.hasMarket(m)) {
                                continue;
                            }
                
                            Types.Wei memory userWei = state.getWei(account, m);
                
                            if (userWei.isZero()) {
                                continue;
                            }
                
                            uint256 assetValue = userWei.value.mul(cache.getPrice(m).value);
                            Decimal.D256 memory adjust = Decimal.one();
                            if (adjustForLiquidity) {
                                adjust = Decimal.onePlus(state.markets[m].marginPremium);
                            }
                
                            if (userWei.sign) {
                                supplyValue.value = supplyValue.value.add(Decimal.div(assetValue, adjust));
                            } else {
                                borrowValue.value = borrowValue.value.add(Decimal.mul(assetValue, adjust));
                            }
                        }
                
                        return (supplyValue, borrowValue);
                    }
                
                    function isCollateralized(
                        Storage.State storage state,
                        Account.Info memory account,
                        Cache.MarketCache memory cache,
                        bool requireMinBorrow
                    )
                        internal
                        view
                        returns (bool)
                    {
                        // get account values (adjusted for liquidity)
                        (
                            Monetary.Value memory supplyValue,
                            Monetary.Value memory borrowValue
                        ) = state.getAccountValues(account, cache, /* adjustForLiquidity = */ true);
                
                        if (borrowValue.value == 0) {
                            return true;
                        }
                
                        if (requireMinBorrow) {
                            Require.that(
                                borrowValue.value >= state.riskParams.minBorrowedValue.value,
                                FILE,
                                "Borrow value too low",
                                account.owner,
                                account.number,
                                borrowValue.value
                            );
                        }
                
                        uint256 requiredMargin = Decimal.mul(borrowValue.value, state.riskParams.marginRatio);
                
                        return supplyValue.value >= borrowValue.value.add(requiredMargin);
                    }
                
                    function isGlobalOperator(
                        Storage.State storage state,
                        address operator
                    )
                        internal
                        view
                        returns (bool)
                    {
                        return state.globalOperators[operator];
                    }
                
                    function isLocalOperator(
                        Storage.State storage state,
                        address owner,
                        address operator
                    )
                        internal
                        view
                        returns (bool)
                    {
                        return state.operators[owner][operator];
                    }
                
                    function requireIsOperator(
                        Storage.State storage state,
                        Account.Info memory account,
                        address operator
                    )
                        internal
                        view
                    {
                        bool isValidOperator =
                            operator == account.owner
                            || state.isGlobalOperator(operator)
                            || state.isLocalOperator(account.owner, operator);
                
                        Require.that(
                            isValidOperator,
                            FILE,
                            "Unpermissioned operator",
                            operator
                        );
                    }
                
                    /**
                     * Determine and set an account's balance based on the intended balance change. Return the
                     * equivalent amount in wei
                     */
                    function getNewParAndDeltaWei(
                        Storage.State storage state,
                        Account.Info memory account,
                        uint256 marketId,
                        Types.AssetAmount memory amount
                    )
                        internal
                        view
                        returns (Types.Par memory, Types.Wei memory)
                    {
                        Types.Par memory oldPar = state.getPar(account, marketId);
                
                        if (amount.value == 0 && amount.ref == Types.AssetReference.Delta) {
                            return (oldPar, Types.zeroWei());
                        }
                
                        Interest.Index memory index = state.getIndex(marketId);
                        Types.Wei memory oldWei = Interest.parToWei(oldPar, index);
                        Types.Par memory newPar;
                        Types.Wei memory deltaWei;
                
                        if (amount.denomination == Types.AssetDenomination.Wei) {
                            deltaWei = Types.Wei({
                                sign: amount.sign,
                                value: amount.value
                            });
                            if (amount.ref == Types.AssetReference.Target) {
                                deltaWei = deltaWei.sub(oldWei);
                            }
                            newPar = Interest.weiToPar(oldWei.add(deltaWei), index);
                        } else { // AssetDenomination.Par
                            newPar = Types.Par({
                                sign: amount.sign,
                                value: amount.value.to128()
                            });
                            if (amount.ref == Types.AssetReference.Delta) {
                                newPar = oldPar.add(newPar);
                            }
                            deltaWei = Interest.parToWei(newPar, index).sub(oldWei);
                        }
                
                        return (newPar, deltaWei);
                    }
                
                    function getNewParAndDeltaWeiForLiquidation(
                        Storage.State storage state,
                        Account.Info memory account,
                        uint256 marketId,
                        Types.AssetAmount memory amount
                    )
                        internal
                        view
                        returns (Types.Par memory, Types.Wei memory)
                    {
                        Types.Par memory oldPar = state.getPar(account, marketId);
                
                        Require.that(
                            !oldPar.isPositive(),
                            FILE,
                            "Owed balance cannot be positive",
                            account.owner,
                            account.number,
                            marketId
                        );
                
                        (
                            Types.Par memory newPar,
                            Types.Wei memory deltaWei
                        ) = state.getNewParAndDeltaWei(
                            account,
                            marketId,
                            amount
                        );
                
                        // if attempting to over-repay the owed asset, bound it by the maximum
                        if (newPar.isPositive()) {
                            newPar = Types.zeroPar();
                            deltaWei = state.getWei(account, marketId).negative();
                        }
                
                        Require.that(
                            !deltaWei.isNegative() && oldPar.value >= newPar.value,
                            FILE,
                            "Owed balance cannot increase",
                            account.owner,
                            account.number,
                            marketId
                        );
                
                        // if not paying back enough wei to repay any par, then bound wei to zero
                        if (oldPar.equals(newPar)) {
                            deltaWei = Types.zeroWei();
                        }
                
                        return (newPar, deltaWei);
                    }
                
                    function isVaporizable(
                        Storage.State storage state,
                        Account.Info memory account,
                        Cache.MarketCache memory cache
                    )
                        internal
                        view
                        returns (bool)
                    {
                        bool hasNegative = false;
                        uint256 numMarkets = cache.getNumMarkets();
                        for (uint256 m = 0; m < numMarkets; m++) {
                            if (!cache.hasMarket(m)) {
                                continue;
                            }
                            Types.Par memory par = state.getPar(account, m);
                            if (par.isZero()) {
                                continue;
                            } else if (par.sign) {
                                return false;
                            } else {
                                hasNegative = true;
                            }
                        }
                        return hasNegative;
                    }
                
                    // =============== Setter Functions ===============
                
                    function updateIndex(
                        Storage.State storage state,
                        uint256 marketId
                    )
                        internal
                        returns (Interest.Index memory)
                    {
                        Interest.Index memory index = state.getIndex(marketId);
                        if (index.lastUpdate == Time.currentTime()) {
                            return index;
                        }
                        return state.markets[marketId].index = state.fetchNewIndex(marketId, index);
                    }
                
                    function setStatus(
                        Storage.State storage state,
                        Account.Info memory account,
                        Account.Status status
                    )
                        internal
                    {
                        state.accounts[account.owner][account.number].status = status;
                    }
                
                    function setPar(
                        Storage.State storage state,
                        Account.Info memory account,
                        uint256 marketId,
                        Types.Par memory newPar
                    )
                        internal
                    {
                        Types.Par memory oldPar = state.getPar(account, marketId);
                
                        if (Types.equals(oldPar, newPar)) {
                            return;
                        }
                
                        // updateTotalPar
                        Types.TotalPar memory totalPar = state.getTotalPar(marketId);
                
                        // roll-back oldPar
                        if (oldPar.sign) {
                            totalPar.supply = uint256(totalPar.supply).sub(oldPar.value).to128();
                        } else {
                            totalPar.borrow = uint256(totalPar.borrow).sub(oldPar.value).to128();
                        }
                
                        // roll-forward newPar
                        if (newPar.sign) {
                            totalPar.supply = uint256(totalPar.supply).add(newPar.value).to128();
                        } else {
                            totalPar.borrow = uint256(totalPar.borrow).add(newPar.value).to128();
                        }
                
                        state.markets[marketId].totalPar = totalPar;
                        state.accounts[account.owner][account.number].balances[marketId] = newPar;
                    }
                
                    /**
                     * Determine and set an account's balance based on a change in wei
                     */
                    function setParFromDeltaWei(
                        Storage.State storage state,
                        Account.Info memory account,
                        uint256 marketId,
                        Types.Wei memory deltaWei
                    )
                        internal
                    {
                        if (deltaWei.isZero()) {
                            return;
                        }
                        Interest.Index memory index = state.getIndex(marketId);
                        Types.Wei memory oldWei = state.getWei(account, marketId);
                        Types.Wei memory newWei = oldWei.add(deltaWei);
                        Types.Par memory newPar = Interest.weiToPar(newWei, index);
                        state.setPar(
                            account,
                            marketId,
                            newPar
                        );
                    }
                }
                
                // File: contracts/protocol/State.sol
                
                /**
                 * @title State
                 * @author dYdX
                 *
                 * Base-level contract that holds the state of Solo
                 */
                contract State
                {
                    Storage.State g_state;
                }
                
                // File: contracts/protocol/impl/AdminImpl.sol
                
                /**
                 * @title AdminImpl
                 * @author dYdX
                 *
                 * Administrative functions to keep the protocol updated
                 */
                library AdminImpl {
                    using Storage for Storage.State;
                    using Token for address;
                    using Types for Types.Wei;
                
                    // ============ Constants ============
                
                    bytes32 constant FILE = "AdminImpl";
                
                    // ============ Events ============
                
                    event LogWithdrawExcessTokens(
                        address token,
                        uint256 amount
                    );
                
                    event LogAddMarket(
                        uint256 marketId,
                        address token
                    );
                
                    event LogSetIsClosing(
                        uint256 marketId,
                        bool isClosing
                    );
                
                    event LogSetPriceOracle(
                        uint256 marketId,
                        address priceOracle
                    );
                
                    event LogSetInterestSetter(
                        uint256 marketId,
                        address interestSetter
                    );
                
                    event LogSetMarginPremium(
                        uint256 marketId,
                        Decimal.D256 marginPremium
                    );
                
                    event LogSetSpreadPremium(
                        uint256 marketId,
                        Decimal.D256 spreadPremium
                    );
                
                    event LogSetMarginRatio(
                        Decimal.D256 marginRatio
                    );
                
                    event LogSetLiquidationSpread(
                        Decimal.D256 liquidationSpread
                    );
                
                    event LogSetEarningsRate(
                        Decimal.D256 earningsRate
                    );
                
                    event LogSetMinBorrowedValue(
                        Monetary.Value minBorrowedValue
                    );
                
                    event LogSetGlobalOperator(
                        address operator,
                        bool approved
                    );
                
                    // ============ Token Functions ============
                
                    function ownerWithdrawExcessTokens(
                        Storage.State storage state,
                        uint256 marketId,
                        address recipient
                    )
                        public
                        returns (uint256)
                    {
                        _validateMarketId(state, marketId);
                        Types.Wei memory excessWei = state.getNumExcessTokens(marketId);
                
                        Require.that(
                            !excessWei.isNegative(),
                            FILE,
                            "Negative excess"
                        );
                
                        address token = state.getToken(marketId);
                
                        uint256 actualBalance = token.balanceOf(address(this));
                        if (excessWei.value > actualBalance) {
                            excessWei.value = actualBalance;
                        }
                
                        token.transfer(recipient, excessWei.value);
                
                        emit LogWithdrawExcessTokens(token, excessWei.value);
                
                        return excessWei.value;
                    }
                
                    function ownerWithdrawUnsupportedTokens(
                        Storage.State storage state,
                        address token,
                        address recipient
                    )
                        public
                        returns (uint256)
                    {
                        _requireNoMarket(state, token);
                
                        uint256 balance = token.balanceOf(address(this));
                        token.transfer(recipient, balance);
                
                        emit LogWithdrawExcessTokens(token, balance);
                
                        return balance;
                    }
                
                    // ============ Market Functions ============
                
                    function ownerAddMarket(
                        Storage.State storage state,
                        address token,
                        IPriceOracle priceOracle,
                        IInterestSetter interestSetter,
                        Decimal.D256 memory marginPremium,
                        Decimal.D256 memory spreadPremium
                    )
                        public
                    {
                        _requireNoMarket(state, token);
                
                        uint256 marketId = state.numMarkets;
                
                        state.numMarkets++;
                        state.markets[marketId].token = token;
                        state.markets[marketId].index = Interest.newIndex();
                
                        emit LogAddMarket(marketId, token);
                
                        _setPriceOracle(state, marketId, priceOracle);
                        _setInterestSetter(state, marketId, interestSetter);
                        _setMarginPremium(state, marketId, marginPremium);
                        _setSpreadPremium(state, marketId, spreadPremium);
                    }
                
                    function ownerSetIsClosing(
                        Storage.State storage state,
                        uint256 marketId,
                        bool isClosing
                    )
                        public
                    {
                        _validateMarketId(state, marketId);
                        state.markets[marketId].isClosing = isClosing;
                        emit LogSetIsClosing(marketId, isClosing);
                    }
                
                    function ownerSetPriceOracle(
                        Storage.State storage state,
                        uint256 marketId,
                        IPriceOracle priceOracle
                    )
                        public
                    {
                        _validateMarketId(state, marketId);
                        _setPriceOracle(state, marketId, priceOracle);
                    }
                
                    function ownerSetInterestSetter(
                        Storage.State storage state,
                        uint256 marketId,
                        IInterestSetter interestSetter
                    )
                        public
                    {
                        _validateMarketId(state, marketId);
                        _setInterestSetter(state, marketId, interestSetter);
                    }
                
                    function ownerSetMarginPremium(
                        Storage.State storage state,
                        uint256 marketId,
                        Decimal.D256 memory marginPremium
                    )
                        public
                    {
                        _validateMarketId(state, marketId);
                        _setMarginPremium(state, marketId, marginPremium);
                    }
                
                    function ownerSetSpreadPremium(
                        Storage.State storage state,
                        uint256 marketId,
                        Decimal.D256 memory spreadPremium
                    )
                        public
                    {
                        _validateMarketId(state, marketId);
                        _setSpreadPremium(state, marketId, spreadPremium);
                    }
                
                    // ============ Risk Functions ============
                
                    function ownerSetMarginRatio(
                        Storage.State storage state,
                        Decimal.D256 memory ratio
                    )
                        public
                    {
                        Require.that(
                            ratio.value <= state.riskLimits.marginRatioMax,
                            FILE,
                            "Ratio too high"
                        );
                        Require.that(
                            ratio.value > state.riskParams.liquidationSpread.value,
                            FILE,
                            "Ratio cannot be <= spread"
                        );
                        state.riskParams.marginRatio = ratio;
                        emit LogSetMarginRatio(ratio);
                    }
                
                    function ownerSetLiquidationSpread(
                        Storage.State storage state,
                        Decimal.D256 memory spread
                    )
                        public
                    {
                        Require.that(
                            spread.value <= state.riskLimits.liquidationSpreadMax,
                            FILE,
                            "Spread too high"
                        );
                        Require.that(
                            spread.value < state.riskParams.marginRatio.value,
                            FILE,
                            "Spread cannot be >= ratio"
                        );
                        state.riskParams.liquidationSpread = spread;
                        emit LogSetLiquidationSpread(spread);
                    }
                
                    function ownerSetEarningsRate(
                        Storage.State storage state,
                        Decimal.D256 memory earningsRate
                    )
                        public
                    {
                        Require.that(
                            earningsRate.value <= state.riskLimits.earningsRateMax,
                            FILE,
                            "Rate too high"
                        );
                        state.riskParams.earningsRate = earningsRate;
                        emit LogSetEarningsRate(earningsRate);
                    }
                
                    function ownerSetMinBorrowedValue(
                        Storage.State storage state,
                        Monetary.Value memory minBorrowedValue
                    )
                        public
                    {
                        Require.that(
                            minBorrowedValue.value <= state.riskLimits.minBorrowedValueMax,
                            FILE,
                            "Value too high"
                        );
                        state.riskParams.minBorrowedValue = minBorrowedValue;
                        emit LogSetMinBorrowedValue(minBorrowedValue);
                    }
                
                    // ============ Global Operator Functions ============
                
                    function ownerSetGlobalOperator(
                        Storage.State storage state,
                        address operator,
                        bool approved
                    )
                        public
                    {
                        state.globalOperators[operator] = approved;
                
                        emit LogSetGlobalOperator(operator, approved);
                    }
                
                    // ============ Private Functions ============
                
                    function _setPriceOracle(
                        Storage.State storage state,
                        uint256 marketId,
                        IPriceOracle priceOracle
                    )
                        private
                    {
                        // require oracle can return non-zero price
                        address token = state.markets[marketId].token;
                
                        Require.that(
                            priceOracle.getPrice(token).value != 0,
                            FILE,
                            "Invalid oracle price"
                        );
                
                        state.markets[marketId].priceOracle = priceOracle;
                
                        emit LogSetPriceOracle(marketId, address(priceOracle));
                    }
                
                    function _setInterestSetter(
                        Storage.State storage state,
                        uint256 marketId,
                        IInterestSetter interestSetter
                    )
                        private
                    {
                        // ensure interestSetter can return a value without reverting
                        address token = state.markets[marketId].token;
                        interestSetter.getInterestRate(token, 0, 0);
                
                        state.markets[marketId].interestSetter = interestSetter;
                
                        emit LogSetInterestSetter(marketId, address(interestSetter));
                    }
                
                    function _setMarginPremium(
                        Storage.State storage state,
                        uint256 marketId,
                        Decimal.D256 memory marginPremium
                    )
                        private
                    {
                        Require.that(
                            marginPremium.value <= state.riskLimits.marginPremiumMax,
                            FILE,
                            "Margin premium too high"
                        );
                        state.markets[marketId].marginPremium = marginPremium;
                
                        emit LogSetMarginPremium(marketId, marginPremium);
                    }
                
                    function _setSpreadPremium(
                        Storage.State storage state,
                        uint256 marketId,
                        Decimal.D256 memory spreadPremium
                    )
                        private
                    {
                        Require.that(
                            spreadPremium.value <= state.riskLimits.spreadPremiumMax,
                            FILE,
                            "Spread premium too high"
                        );
                        state.markets[marketId].spreadPremium = spreadPremium;
                
                        emit LogSetSpreadPremium(marketId, spreadPremium);
                    }
                
                    function _requireNoMarket(
                        Storage.State storage state,
                        address token
                    )
                        private
                        view
                    {
                        uint256 numMarkets = state.numMarkets;
                
                        bool marketExists = false;
                
                        for (uint256 m = 0; m < numMarkets; m++) {
                            if (state.markets[m].token == token) {
                                marketExists = true;
                                break;
                            }
                        }
                
                        Require.that(
                            !marketExists,
                            FILE,
                            "Market exists"
                        );
                    }
                
                    function _validateMarketId(
                        Storage.State storage state,
                        uint256 marketId
                    )
                        private
                        view
                    {
                        Require.that(
                            marketId < state.numMarkets,
                            FILE,
                            "Market OOB",
                            marketId
                        );
                    }
                }
                
                // File: contracts/protocol/Admin.sol
                
                /**
                 * @title Admin
                 * @author dYdX
                 *
                 * Public functions that allow the privileged owner address to manage Solo
                 */
                contract Admin is
                    State,
                    Ownable,
                    ReentrancyGuard
                {
                    // ============ Token Functions ============
                
                    /**
                     * Withdraw an ERC20 token for which there is an associated market. Only excess tokens can be
                     * withdrawn. The number of excess tokens is calculated by taking the current number of tokens
                     * held in Solo, adding the number of tokens owed to Solo by borrowers, and subtracting the
                     * number of tokens owed to suppliers by Solo.
                     */
                    function ownerWithdrawExcessTokens(
                        uint256 marketId,
                        address recipient
                    )
                        public
                        onlyOwner
                        nonReentrant
                        returns (uint256)
                    {
                        return AdminImpl.ownerWithdrawExcessTokens(
                            g_state,
                            marketId,
                            recipient
                        );
                    }
                
                    /**
                     * Withdraw an ERC20 token for which there is no associated market.
                     */
                    function ownerWithdrawUnsupportedTokens(
                        address token,
                        address recipient
                    )
                        public
                        onlyOwner
                        nonReentrant
                        returns (uint256)
                    {
                        return AdminImpl.ownerWithdrawUnsupportedTokens(
                            g_state,
                            token,
                            recipient
                        );
                    }
                
                    // ============ Market Functions ============
                
                    /**
                     * Add a new market to Solo. Must be for a previously-unsupported ERC20 token.
                     */
                    function ownerAddMarket(
                        address token,
                        IPriceOracle priceOracle,
                        IInterestSetter interestSetter,
                        Decimal.D256 memory marginPremium,
                        Decimal.D256 memory spreadPremium
                    )
                        public
                        onlyOwner
                        nonReentrant
                    {
                        AdminImpl.ownerAddMarket(
                            g_state,
                            token,
                            priceOracle,
                            interestSetter,
                            marginPremium,
                            spreadPremium
                        );
                    }
                
                    /**
                     * Set (or unset) the status of a market to "closing". The borrowedValue of a market cannot
                     * increase while its status is "closing".
                     */
                    function ownerSetIsClosing(
                        uint256 marketId,
                        bool isClosing
                    )
                        public
                        onlyOwner
                        nonReentrant
                    {
                        AdminImpl.ownerSetIsClosing(
                            g_state,
                            marketId,
                            isClosing
                        );
                    }
                
                    /**
                     * Set the price oracle for a market.
                     */
                    function ownerSetPriceOracle(
                        uint256 marketId,
                        IPriceOracle priceOracle
                    )
                        public
                        onlyOwner
                        nonReentrant
                    {
                        AdminImpl.ownerSetPriceOracle(
                            g_state,
                            marketId,
                            priceOracle
                        );
                    }
                
                    /**
                     * Set the interest-setter for a market.
                     */
                    function ownerSetInterestSetter(
                        uint256 marketId,
                        IInterestSetter interestSetter
                    )
                        public
                        onlyOwner
                        nonReentrant
                    {
                        AdminImpl.ownerSetInterestSetter(
                            g_state,
                            marketId,
                            interestSetter
                        );
                    }
                
                    /**
                     * Set a premium on the minimum margin-ratio for a market. This makes it so that any positions
                     * that include this market require a higher collateralization to avoid being liquidated.
                     */
                    function ownerSetMarginPremium(
                        uint256 marketId,
                        Decimal.D256 memory marginPremium
                    )
                        public
                        onlyOwner
                        nonReentrant
                    {
                        AdminImpl.ownerSetMarginPremium(
                            g_state,
                            marketId,
                            marginPremium
                        );
                    }
                
                    /**
                     * Set a premium on the liquidation spread for a market. This makes it so that any liquidations
                     * that include this market have a higher spread than the global default.
                     */
                    function ownerSetSpreadPremium(
                        uint256 marketId,
                        Decimal.D256 memory spreadPremium
                    )
                        public
                        onlyOwner
                        nonReentrant
                    {
                        AdminImpl.ownerSetSpreadPremium(
                            g_state,
                            marketId,
                            spreadPremium
                        );
                    }
                
                    // ============ Risk Functions ============
                
                    /**
                     * Set the global minimum margin-ratio that every position must maintain to prevent being
                     * liquidated.
                     */
                    function ownerSetMarginRatio(
                        Decimal.D256 memory ratio
                    )
                        public
                        onlyOwner
                        nonReentrant
                    {
                        AdminImpl.ownerSetMarginRatio(
                            g_state,
                            ratio
                        );
                    }
                
                    /**
                     * Set the global liquidation spread. This is the spread between oracle prices that incentivizes
                     * the liquidation of risky positions.
                     */
                    function ownerSetLiquidationSpread(
                        Decimal.D256 memory spread
                    )
                        public
                        onlyOwner
                        nonReentrant
                    {
                        AdminImpl.ownerSetLiquidationSpread(
                            g_state,
                            spread
                        );
                    }
                
                    /**
                     * Set the global earnings-rate variable that determines what percentage of the interest paid
                     * by borrowers gets passed-on to suppliers.
                     */
                    function ownerSetEarningsRate(
                        Decimal.D256 memory earningsRate
                    )
                        public
                        onlyOwner
                        nonReentrant
                    {
                        AdminImpl.ownerSetEarningsRate(
                            g_state,
                            earningsRate
                        );
                    }
                
                    /**
                     * Set the global minimum-borrow value which is the minimum value of any new borrow on Solo.
                     */
                    function ownerSetMinBorrowedValue(
                        Monetary.Value memory minBorrowedValue
                    )
                        public
                        onlyOwner
                        nonReentrant
                    {
                        AdminImpl.ownerSetMinBorrowedValue(
                            g_state,
                            minBorrowedValue
                        );
                    }
                
                    // ============ Global Operator Functions ============
                
                    /**
                     * Approve (or disapprove) an address that is permissioned to be an operator for all accounts in
                     * Solo. Intended only to approve smart-contracts.
                     */
                    function ownerSetGlobalOperator(
                        address operator,
                        bool approved
                    )
                        public
                        onlyOwner
                        nonReentrant
                    {
                        AdminImpl.ownerSetGlobalOperator(
                            g_state,
                            operator,
                            approved
                        );
                    }
                }
                
                // File: contracts/protocol/Getters.sol
                
                /**
                 * @title Getters
                 * @author dYdX
                 *
                 * Public read-only functions that allow transparency into the state of Solo
                 */
                contract Getters is
                    State
                {
                    using Cache for Cache.MarketCache;
                    using Storage for Storage.State;
                    using Types for Types.Par;
                
                    // ============ Constants ============
                
                    bytes32 FILE = "Getters";
                
                    // ============ Getters for Risk ============
                
                    /**
                     * Get the global minimum margin-ratio that every position must maintain to prevent being
                     * liquidated.
                     *
                     * @return  The global margin-ratio
                     */
                    function getMarginRatio()
                        public
                        view
                        returns (Decimal.D256 memory)
                    {
                        return g_state.riskParams.marginRatio;
                    }
                
                    /**
                     * Get the global liquidation spread. This is the spread between oracle prices that incentivizes
                     * the liquidation of risky positions.
                     *
                     * @return  The global liquidation spread
                     */
                    function getLiquidationSpread()
                        public
                        view
                        returns (Decimal.D256 memory)
                    {
                        return g_state.riskParams.liquidationSpread;
                    }
                
                    /**
                     * Get the global earnings-rate variable that determines what percentage of the interest paid
                     * by borrowers gets passed-on to suppliers.
                     *
                     * @return  The global earnings rate
                     */
                    function getEarningsRate()
                        public
                        view
                        returns (Decimal.D256 memory)
                    {
                        return g_state.riskParams.earningsRate;
                    }
                
                    /**
                     * Get the global minimum-borrow value which is the minimum value of any new borrow on Solo.
                     *
                     * @return  The global minimum borrow value
                     */
                    function getMinBorrowedValue()
                        public
                        view
                        returns (Monetary.Value memory)
                    {
                        return g_state.riskParams.minBorrowedValue;
                    }
                
                    /**
                     * Get all risk parameters in a single struct.
                     *
                     * @return  All global risk parameters
                     */
                    function getRiskParams()
                        public
                        view
                        returns (Storage.RiskParams memory)
                    {
                        return g_state.riskParams;
                    }
                
                    /**
                     * Get all risk parameter limits in a single struct. These are the maximum limits at which the
                     * risk parameters can be set by the admin of Solo.
                     *
                     * @return  All global risk parameter limnits
                     */
                    function getRiskLimits()
                        public
                        view
                        returns (Storage.RiskLimits memory)
                    {
                        return g_state.riskLimits;
                    }
                
                    // ============ Getters for Markets ============
                
                    /**
                     * Get the total number of markets.
                     *
                     * @return  The number of markets
                     */
                    function getNumMarkets()
                        public
                        view
                        returns (uint256)
                    {
                        return g_state.numMarkets;
                    }
                
                    /**
                     * Get the ERC20 token address for a market.
                     *
                     * @param  marketId  The market to query
                     * @return           The token address
                     */
                    function getMarketTokenAddress(
                        uint256 marketId
                    )
                        public
                        view
                        returns (address)
                    {
                        _requireValidMarket(marketId);
                        return g_state.getToken(marketId);
                    }
                
                    /**
                     * Get the total principal amounts (borrowed and supplied) for a market.
                     *
                     * @param  marketId  The market to query
                     * @return           The total principal amounts
                     */
                    function getMarketTotalPar(
                        uint256 marketId
                    )
                        public
                        view
                        returns (Types.TotalPar memory)
                    {
                        _requireValidMarket(marketId);
                        return g_state.getTotalPar(marketId);
                    }
                
                    /**
                     * Get the most recently cached interest index for a market.
                     *
                     * @param  marketId  The market to query
                     * @return           The most recent index
                     */
                    function getMarketCachedIndex(
                        uint256 marketId
                    )
                        public
                        view
                        returns (Interest.Index memory)
                    {
                        _requireValidMarket(marketId);
                        return g_state.getIndex(marketId);
                    }
                
                    /**
                     * Get the interest index for a market if it were to be updated right now.
                     *
                     * @param  marketId  The market to query
                     * @return           The estimated current index
                     */
                    function getMarketCurrentIndex(
                        uint256 marketId
                    )
                        public
                        view
                        returns (Interest.Index memory)
                    {
                        _requireValidMarket(marketId);
                        return g_state.fetchNewIndex(marketId, g_state.getIndex(marketId));
                    }
                
                    /**
                     * Get the price oracle address for a market.
                     *
                     * @param  marketId  The market to query
                     * @return           The price oracle address
                     */
                    function getMarketPriceOracle(
                        uint256 marketId
                    )
                        public
                        view
                        returns (IPriceOracle)
                    {
                        _requireValidMarket(marketId);
                        return g_state.markets[marketId].priceOracle;
                    }
                
                    /**
                     * Get the interest-setter address for a market.
                     *
                     * @param  marketId  The market to query
                     * @return           The interest-setter address
                     */
                    function getMarketInterestSetter(
                        uint256 marketId
                    )
                        public
                        view
                        returns (IInterestSetter)
                    {
                        _requireValidMarket(marketId);
                        return g_state.markets[marketId].interestSetter;
                    }
                
                    /**
                     * Get the margin premium for a market. A margin premium makes it so that any positions that
                     * include the market require a higher collateralization to avoid being liquidated.
                     *
                     * @param  marketId  The market to query
                     * @return           The market's margin premium
                     */
                    function getMarketMarginPremium(
                        uint256 marketId
                    )
                        public
                        view
                        returns (Decimal.D256 memory)
                    {
                        _requireValidMarket(marketId);
                        return g_state.markets[marketId].marginPremium;
                    }
                
                    /**
                     * Get the spread premium for a market. A spread premium makes it so that any liquidations
                     * that include the market have a higher spread than the global default.
                     *
                     * @param  marketId  The market to query
                     * @return           The market's spread premium
                     */
                    function getMarketSpreadPremium(
                        uint256 marketId
                    )
                        public
                        view
                        returns (Decimal.D256 memory)
                    {
                        _requireValidMarket(marketId);
                        return g_state.markets[marketId].spreadPremium;
                    }
                
                    /**
                     * Return true if a particular market is in closing mode. Additional borrows cannot be taken
                     * from a market that is closing.
                     *
                     * @param  marketId  The market to query
                     * @return           True if the market is closing
                     */
                    function getMarketIsClosing(
                        uint256 marketId
                    )
                        public
                        view
                        returns (bool)
                    {
                        _requireValidMarket(marketId);
                        return g_state.markets[marketId].isClosing;
                    }
                
                    /**
                     * Get the price of the token for a market.
                     *
                     * @param  marketId  The market to query
                     * @return           The price of each atomic unit of the token
                     */
                    function getMarketPrice(
                        uint256 marketId
                    )
                        public
                        view
                        returns (Monetary.Price memory)
                    {
                        _requireValidMarket(marketId);
                        return g_state.fetchPrice(marketId);
                    }
                
                    /**
                     * Get the current borrower interest rate for a market.
                     *
                     * @param  marketId  The market to query
                     * @return           The current interest rate
                     */
                    function getMarketInterestRate(
                        uint256 marketId
                    )
                        public
                        view
                        returns (Interest.Rate memory)
                    {
                        _requireValidMarket(marketId);
                        return g_state.fetchInterestRate(
                            marketId,
                            g_state.getIndex(marketId)
                        );
                    }
                
                    /**
                     * Get the adjusted liquidation spread for some market pair. This is equal to the global
                     * liquidation spread multiplied by (1 + spreadPremium) for each of the two markets.
                     *
                     * @param  heldMarketId  The market for which the account has collateral
                     * @param  owedMarketId  The market for which the account has borrowed tokens
                     * @return               The adjusted liquidation spread
                     */
                    function getLiquidationSpreadForPair(
                        uint256 heldMarketId,
                        uint256 owedMarketId
                    )
                        public
                        view
                        returns (Decimal.D256 memory)
                    {
                        _requireValidMarket(heldMarketId);
                        _requireValidMarket(owedMarketId);
                        return g_state.getLiquidationSpreadForPair(heldMarketId, owedMarketId);
                    }
                
                    /**
                     * Get basic information about a particular market.
                     *
                     * @param  marketId  The market to query
                     * @return           A Storage.Market struct with the current state of the market
                     */
                    function getMarket(
                        uint256 marketId
                    )
                        public
                        view
                        returns (Storage.Market memory)
                    {
                        _requireValidMarket(marketId);
                        return g_state.markets[marketId];
                    }
                
                    /**
                     * Get comprehensive information about a particular market.
                     *
                     * @param  marketId  The market to query
                     * @return           A tuple containing the values:
                     *                    - A Storage.Market struct with the current state of the market
                     *                    - The current estimated interest index
                     *                    - The current token price
                     *                    - The current market interest rate
                     */
                    function getMarketWithInfo(
                        uint256 marketId
                    )
                        public
                        view
                        returns (
                            Storage.Market memory,
                            Interest.Index memory,
                            Monetary.Price memory,
                            Interest.Rate memory
                        )
                    {
                        _requireValidMarket(marketId);
                        return (
                            getMarket(marketId),
                            getMarketCurrentIndex(marketId),
                            getMarketPrice(marketId),
                            getMarketInterestRate(marketId)
                        );
                    }
                
                    /**
                     * Get the number of excess tokens for a market. The number of excess tokens is calculated
                     * by taking the current number of tokens held in Solo, adding the number of tokens owed to Solo
                     * by borrowers, and subtracting the number of tokens owed to suppliers by Solo.
                     *
                     * @param  marketId  The market to query
                     * @return           The number of excess tokens
                     */
                    function getNumExcessTokens(
                        uint256 marketId
                    )
                        public
                        view
                        returns (Types.Wei memory)
                    {
                        _requireValidMarket(marketId);
                        return g_state.getNumExcessTokens(marketId);
                    }
                
                    // ============ Getters for Accounts ============
                
                    /**
                     * Get the principal value for a particular account and market.
                     *
                     * @param  account   The account to query
                     * @param  marketId  The market to query
                     * @return           The principal value
                     */
                    function getAccountPar(
                        Account.Info memory account,
                        uint256 marketId
                    )
                        public
                        view
                        returns (Types.Par memory)
                    {
                        _requireValidMarket(marketId);
                        return g_state.getPar(account, marketId);
                    }
                
                    /**
                     * Get the token balance for a particular account and market.
                     *
                     * @param  account   The account to query
                     * @param  marketId  The market to query
                     * @return           The token amount
                     */
                    function getAccountWei(
                        Account.Info memory account,
                        uint256 marketId
                    )
                        public
                        view
                        returns (Types.Wei memory)
                    {
                        _requireValidMarket(marketId);
                        return Interest.parToWei(
                            g_state.getPar(account, marketId),
                            g_state.fetchNewIndex(marketId, g_state.getIndex(marketId))
                        );
                    }
                
                    /**
                     * Get the status of an account (Normal, Liquidating, or Vaporizing).
                     *
                     * @param  account  The account to query
                     * @return          The account's status
                     */
                    function getAccountStatus(
                        Account.Info memory account
                    )
                        public
                        view
                        returns (Account.Status)
                    {
                        return g_state.getStatus(account);
                    }
                
                    /**
                     * Get the total supplied and total borrowed value of an account.
                     *
                     * @param  account  The account to query
                     * @return          The following values:
                     *                   - The supplied value of the account
                     *                   - The borrowed value of the account
                     */
                    function getAccountValues(
                        Account.Info memory account
                    )
                        public
                        view
                        returns (Monetary.Value memory, Monetary.Value memory)
                    {
                        return getAccountValuesInternal(account, /* adjustForLiquidity = */ false);
                    }
                
                    /**
                     * Get the total supplied and total borrowed values of an account adjusted by the marginPremium
                     * of each market. Supplied values are divided by (1 + marginPremium) for each market and
                     * borrowed values are multiplied by (1 + marginPremium) for each market. Comparing these
                     * adjusted values gives the margin-ratio of the account which will be compared to the global
                     * margin-ratio when determining if the account can be liquidated.
                     *
                     * @param  account  The account to query
                     * @return          The following values:
                     *                   - The supplied value of the account (adjusted for marginPremium)
                     *                   - The borrowed value of the account (adjusted for marginPremium)
                     */
                    function getAdjustedAccountValues(
                        Account.Info memory account
                    )
                        public
                        view
                        returns (Monetary.Value memory, Monetary.Value memory)
                    {
                        return getAccountValuesInternal(account, /* adjustForLiquidity = */ true);
                    }
                
                    /**
                     * Get an account's summary for each market.
                     *
                     * @param  account  The account to query
                     * @return          The following values:
                     *                   - The ERC20 token address for each market
                     *                   - The account's principal value for each market
                     *                   - The account's (supplied or borrowed) number of tokens for each market
                     */
                    function getAccountBalances(
                        Account.Info memory account
                    )
                        public
                        view
                        returns (
                            address[] memory,
                            Types.Par[] memory,
                            Types.Wei[] memory
                        )
                    {
                        uint256 numMarkets = g_state.numMarkets;
                        address[] memory tokens = new address[](numMarkets);
                        Types.Par[] memory pars = new Types.Par[](numMarkets);
                        Types.Wei[] memory weis = new Types.Wei[](numMarkets);
                
                        for (uint256 m = 0; m < numMarkets; m++) {
                            tokens[m] = getMarketTokenAddress(m);
                            pars[m] = getAccountPar(account, m);
                            weis[m] = getAccountWei(account, m);
                        }
                
                        return (
                            tokens,
                            pars,
                            weis
                        );
                    }
                
                    // ============ Getters for Permissions ============
                
                    /**
                     * Return true if a particular address is approved as an operator for an owner's accounts.
                     * Approved operators can act on the accounts of the owner as if it were the operator's own.
                     *
                     * @param  owner     The owner of the accounts
                     * @param  operator  The possible operator
                     * @return           True if operator is approved for owner's accounts
                     */
                    function getIsLocalOperator(
                        address owner,
                        address operator
                    )
                        public
                        view
                        returns (bool)
                    {
                        return g_state.isLocalOperator(owner, operator);
                    }
                
                    /**
                     * Return true if a particular address is approved as a global operator. Such an address can
                     * act on any account as if it were the operator's own.
                     *
                     * @param  operator  The address to query
                     * @return           True if operator is a global operator
                     */
                    function getIsGlobalOperator(
                        address operator
                    )
                        public
                        view
                        returns (bool)
                    {
                        return g_state.isGlobalOperator(operator);
                    }
                
                    // ============ Private Helper Functions ============
                
                    /**
                     * Revert if marketId is invalid.
                     */
                    function _requireValidMarket(
                        uint256 marketId
                    )
                        private
                        view
                    {
                        Require.that(
                            marketId < g_state.numMarkets,
                            FILE,
                            "Market OOB"
                        );
                    }
                
                    /**
                     * Private helper for getting the monetary values of an account.
                     */
                    function getAccountValuesInternal(
                        Account.Info memory account,
                        bool adjustForLiquidity
                    )
                        private
                        view
                        returns (Monetary.Value memory, Monetary.Value memory)
                    {
                        uint256 numMarkets = g_state.numMarkets;
                
                        // populate cache
                        Cache.MarketCache memory cache = Cache.create(numMarkets);
                        for (uint256 m = 0; m < numMarkets; m++) {
                            if (!g_state.getPar(account, m).isZero()) {
                                cache.addMarket(g_state, m);
                            }
                        }
                
                        return g_state.getAccountValues(account, cache, adjustForLiquidity);
                    }
                }
                
                // File: contracts/protocol/interfaces/IAutoTrader.sol
                
                /**
                 * @title IAutoTrader
                 * @author dYdX
                 *
                 * Interface that Auto-Traders for Solo must implement in order to approve trades.
                 */
                contract IAutoTrader {
                
                    // ============ Public Functions ============
                
                    /**
                     * Allows traders to make trades approved by this smart contract. The active trader's account is
                     * the takerAccount and the passive account (for which this contract approves trades
                     * on-behalf-of) is the makerAccount.
                     *
                     * @param  inputMarketId   The market for which the trader specified the original amount
                     * @param  outputMarketId  The market for which the trader wants the resulting amount specified
                     * @param  makerAccount    The account for which this contract is making trades
                     * @param  takerAccount    The account requesting the trade
                     * @param  oldInputPar     The old principal amount for the makerAccount for the inputMarketId
                     * @param  newInputPar     The new principal amount for the makerAccount for the inputMarketId
                     * @param  inputWei        The change in token amount for the makerAccount for the inputMarketId
                     * @param  data            Arbitrary data passed in by the trader
                     * @return                 The AssetAmount for the makerAccount for the outputMarketId
                     */
                    function getTradeCost(
                        uint256 inputMarketId,
                        uint256 outputMarketId,
                        Account.Info memory makerAccount,
                        Account.Info memory takerAccount,
                        Types.Par memory oldInputPar,
                        Types.Par memory newInputPar,
                        Types.Wei memory inputWei,
                        bytes memory data
                    )
                        public
                        returns (Types.AssetAmount memory);
                }
                
                // File: contracts/protocol/interfaces/ICallee.sol
                
                /**
                 * @title ICallee
                 * @author dYdX
                 *
                 * Interface that Callees for Solo must implement in order to ingest data.
                 */
                contract ICallee {
                
                    // ============ Public Functions ============
                
                    /**
                     * Allows users to send this contract arbitrary data.
                     *
                     * @param  sender       The msg.sender to Solo
                     * @param  accountInfo  The account from which the data is being sent
                     * @param  data         Arbitrary data given by the sender
                     */
                    function callFunction(
                        address sender,
                        Account.Info memory accountInfo,
                        bytes memory data
                    )
                        public;
                }
                
                // File: contracts/protocol/lib/Actions.sol
                
                /**
                 * @title Actions
                 * @author dYdX
                 *
                 * Library that defines and parses valid Actions
                 */
                library Actions {
                
                    // ============ Constants ============
                
                    bytes32 constant FILE = "Actions";
                
                    // ============ Enums ============
                
                    enum ActionType {
                        Deposit,   // supply tokens
                        Withdraw,  // borrow tokens
                        Transfer,  // transfer balance between accounts
                        Buy,       // buy an amount of some token (externally)
                        Sell,      // sell an amount of some token (externally)
                        Trade,     // trade tokens against another account
                        Liquidate, // liquidate an undercollateralized or expiring account
                        Vaporize,  // use excess tokens to zero-out a completely negative account
                        Call       // send arbitrary data to an address
                    }
                
                    enum AccountLayout {
                        OnePrimary,
                        TwoPrimary,
                        PrimaryAndSecondary
                    }
                
                    enum MarketLayout {
                        ZeroMarkets,
                        OneMarket,
                        TwoMarkets
                    }
                
                    // ============ Structs ============
                
                    /*
                     * Arguments that are passed to Solo in an ordered list as part of a single operation.
                     * Each ActionArgs has an actionType which specifies which action struct that this data will be
                     * parsed into before being processed.
                     */
                    struct ActionArgs {
                        ActionType actionType;
                        uint256 accountId;
                        Types.AssetAmount amount;
                        uint256 primaryMarketId;
                        uint256 secondaryMarketId;
                        address otherAddress;
                        uint256 otherAccountId;
                        bytes data;
                    }
                
                    // ============ Action Types ============
                
                    /*
                     * Moves tokens from an address to Solo. Can either repay a borrow or provide additional supply.
                     */
                    struct DepositArgs {
                        Types.AssetAmount amount;
                        Account.Info account;
                        uint256 market;
                        address from;
                    }
                
                    /*
                     * Moves tokens from Solo to another address. Can either borrow tokens or reduce the amount
                     * previously supplied.
                     */
                    struct WithdrawArgs {
                        Types.AssetAmount amount;
                        Account.Info account;
                        uint256 market;
                        address to;
                    }
                
                    /*
                     * Transfers balance between two accounts. The msg.sender must be an operator for both accounts.
                     * The amount field applies to accountOne.
                     * This action does not require any token movement since the trade is done internally to Solo.
                     */
                    struct TransferArgs {
                        Types.AssetAmount amount;
                        Account.Info accountOne;
                        Account.Info accountTwo;
                        uint256 market;
                    }
                
                    /*
                     * Acquires a certain amount of tokens by spending other tokens. Sends takerMarket tokens to the
                     * specified exchangeWrapper contract and expects makerMarket tokens in return. The amount field
                     * applies to the makerMarket.
                     */
                    struct BuyArgs {
                        Types.AssetAmount amount;
                        Account.Info account;
                        uint256 makerMarket;
                        uint256 takerMarket;
                        address exchangeWrapper;
                        bytes orderData;
                    }
                
                    /*
                     * Spends a certain amount of tokens to acquire other tokens. Sends takerMarket tokens to the
                     * specified exchangeWrapper and expects makerMarket tokens in return. The amount field applies
                     * to the takerMarket.
                     */
                    struct SellArgs {
                        Types.AssetAmount amount;
                        Account.Info account;
                        uint256 takerMarket;
                        uint256 makerMarket;
                        address exchangeWrapper;
                        bytes orderData;
                    }
                
                    /*
                     * Trades balances between two accounts using any external contract that implements the
                     * AutoTrader interface. The AutoTrader contract must be an operator for the makerAccount (for
                     * which it is trading on-behalf-of). The amount field applies to the makerAccount and the
                     * inputMarket. This proposed change to the makerAccount is passed to the AutoTrader which will
                     * quote a change for the makerAccount in the outputMarket (or will disallow the trade).
                     * This action does not require any token movement since the trade is done internally to Solo.
                     */
                    struct TradeArgs {
                        Types.AssetAmount amount;
                        Account.Info takerAccount;
                        Account.Info makerAccount;
                        uint256 inputMarket;
                        uint256 outputMarket;
                        address autoTrader;
                        bytes tradeData;
                    }
                
                    /*
                     * Each account must maintain a certain margin-ratio (specified globally). If the account falls
                     * below this margin-ratio, it can be liquidated by any other account. This allows anyone else
                     * (arbitrageurs) to repay any borrowed asset (owedMarket) of the liquidating account in
                     * exchange for any collateral asset (heldMarket) of the liquidAccount. The ratio is determined
                     * by the price ratio (given by the oracles) plus a spread (specified globally). Liquidating an
                     * account also sets a flag on the account that the account is being liquidated. This allows
                     * anyone to continue liquidating the account until there are no more borrows being taken by the
                     * liquidating account. Liquidators do not have to liquidate the entire account all at once but
                     * can liquidate as much as they choose. The liquidating flag allows liquidators to continue
                     * liquidating the account even if it becomes collateralized through partial liquidation or
                     * price movement.
                     */
                    struct LiquidateArgs {
                        Types.AssetAmount amount;
                        Account.Info solidAccount;
                        Account.Info liquidAccount;
                        uint256 owedMarket;
                        uint256 heldMarket;
                    }
                
                    /*
                     * Similar to liquidate, but vaporAccounts are accounts that have only negative balances
                     * remaining. The arbitrageur pays back the negative asset (owedMarket) of the vaporAccount in
                     * exchange for a collateral asset (heldMarket) at a favorable spread. However, since the
                     * liquidAccount has no collateral assets, the collateral must come from Solo's excess tokens.
                     */
                    struct VaporizeArgs {
                        Types.AssetAmount amount;
                        Account.Info solidAccount;
                        Account.Info vaporAccount;
                        uint256 owedMarket;
                        uint256 heldMarket;
                    }
                
                    /*
                     * Passes arbitrary bytes of data to an external contract that implements the Callee interface.
                     * Does not change any asset amounts. This function may be useful for setting certain variables
                     * on layer-two contracts for certain accounts without having to make a separate Ethereum
                     * transaction for doing so. Also, the second-layer contracts can ensure that the call is coming
                     * from an operator of the particular account.
                     */
                    struct CallArgs {
                        Account.Info account;
                        address callee;
                        bytes data;
                    }
                
                    // ============ Helper Functions ============
                
                    function getMarketLayout(
                        ActionType actionType
                    )
                        internal
                        pure
                        returns (MarketLayout)
                    {
                        if (
                            actionType == Actions.ActionType.Deposit
                            || actionType == Actions.ActionType.Withdraw
                            || actionType == Actions.ActionType.Transfer
                        ) {
                            return MarketLayout.OneMarket;
                        }
                        else if (actionType == Actions.ActionType.Call) {
                            return MarketLayout.ZeroMarkets;
                        }
                        return MarketLayout.TwoMarkets;
                    }
                
                    function getAccountLayout(
                        ActionType actionType
                    )
                        internal
                        pure
                        returns (AccountLayout)
                    {
                        if (
                            actionType == Actions.ActionType.Transfer
                            || actionType == Actions.ActionType.Trade
                        ) {
                            return AccountLayout.TwoPrimary;
                        } else if (
                            actionType == Actions.ActionType.Liquidate
                            || actionType == Actions.ActionType.Vaporize
                        ) {
                            return AccountLayout.PrimaryAndSecondary;
                        }
                        return AccountLayout.OnePrimary;
                    }
                
                    // ============ Parsing Functions ============
                
                    function parseDepositArgs(
                        Account.Info[] memory accounts,
                        ActionArgs memory args
                    )
                        internal
                        pure
                        returns (DepositArgs memory)
                    {
                        assert(args.actionType == ActionType.Deposit);
                        return DepositArgs({
                            amount: args.amount,
                            account: accounts[args.accountId],
                            market: args.primaryMarketId,
                            from: args.otherAddress
                        });
                    }
                
                    function parseWithdrawArgs(
                        Account.Info[] memory accounts,
                        ActionArgs memory args
                    )
                        internal
                        pure
                        returns (WithdrawArgs memory)
                    {
                        assert(args.actionType == ActionType.Withdraw);
                        return WithdrawArgs({
                            amount: args.amount,
                            account: accounts[args.accountId],
                            market: args.primaryMarketId,
                            to: args.otherAddress
                        });
                    }
                
                    function parseTransferArgs(
                        Account.Info[] memory accounts,
                        ActionArgs memory args
                    )
                        internal
                        pure
                        returns (TransferArgs memory)
                    {
                        assert(args.actionType == ActionType.Transfer);
                        return TransferArgs({
                            amount: args.amount,
                            accountOne: accounts[args.accountId],
                            accountTwo: accounts[args.otherAccountId],
                            market: args.primaryMarketId
                        });
                    }
                
                    function parseBuyArgs(
                        Account.Info[] memory accounts,
                        ActionArgs memory args
                    )
                        internal
                        pure
                        returns (BuyArgs memory)
                    {
                        assert(args.actionType == ActionType.Buy);
                        return BuyArgs({
                            amount: args.amount,
                            account: accounts[args.accountId],
                            makerMarket: args.primaryMarketId,
                            takerMarket: args.secondaryMarketId,
                            exchangeWrapper: args.otherAddress,
                            orderData: args.data
                        });
                    }
                
                    function parseSellArgs(
                        Account.Info[] memory accounts,
                        ActionArgs memory args
                    )
                        internal
                        pure
                        returns (SellArgs memory)
                    {
                        assert(args.actionType == ActionType.Sell);
                        return SellArgs({
                            amount: args.amount,
                            account: accounts[args.accountId],
                            takerMarket: args.primaryMarketId,
                            makerMarket: args.secondaryMarketId,
                            exchangeWrapper: args.otherAddress,
                            orderData: args.data
                        });
                    }
                
                    function parseTradeArgs(
                        Account.Info[] memory accounts,
                        ActionArgs memory args
                    )
                        internal
                        pure
                        returns (TradeArgs memory)
                    {
                        assert(args.actionType == ActionType.Trade);
                        return TradeArgs({
                            amount: args.amount,
                            takerAccount: accounts[args.accountId],
                            makerAccount: accounts[args.otherAccountId],
                            inputMarket: args.primaryMarketId,
                            outputMarket: args.secondaryMarketId,
                            autoTrader: args.otherAddress,
                            tradeData: args.data
                        });
                    }
                
                    function parseLiquidateArgs(
                        Account.Info[] memory accounts,
                        ActionArgs memory args
                    )
                        internal
                        pure
                        returns (LiquidateArgs memory)
                    {
                        assert(args.actionType == ActionType.Liquidate);
                        return LiquidateArgs({
                            amount: args.amount,
                            solidAccount: accounts[args.accountId],
                            liquidAccount: accounts[args.otherAccountId],
                            owedMarket: args.primaryMarketId,
                            heldMarket: args.secondaryMarketId
                        });
                    }
                
                    function parseVaporizeArgs(
                        Account.Info[] memory accounts,
                        ActionArgs memory args
                    )
                        internal
                        pure
                        returns (VaporizeArgs memory)
                    {
                        assert(args.actionType == ActionType.Vaporize);
                        return VaporizeArgs({
                            amount: args.amount,
                            solidAccount: accounts[args.accountId],
                            vaporAccount: accounts[args.otherAccountId],
                            owedMarket: args.primaryMarketId,
                            heldMarket: args.secondaryMarketId
                        });
                    }
                
                    function parseCallArgs(
                        Account.Info[] memory accounts,
                        ActionArgs memory args
                    )
                        internal
                        pure
                        returns (CallArgs memory)
                    {
                        assert(args.actionType == ActionType.Call);
                        return CallArgs({
                            account: accounts[args.accountId],
                            callee: args.otherAddress,
                            data: args.data
                        });
                    }
                }
                
                // File: contracts/protocol/lib/Events.sol
                
                /**
                 * @title Events
                 * @author dYdX
                 *
                 * Library to parse and emit logs from which the state of all accounts and indexes can be followed
                 */
                library Events {
                    using Types for Types.Wei;
                    using Storage for Storage.State;
                
                    // ============ Events ============
                
                    event LogIndexUpdate(
                        uint256 indexed market,
                        Interest.Index index
                    );
                
                    event LogOperation(
                        address sender
                    );
                
                    event LogDeposit(
                        address indexed accountOwner,
                        uint256 accountNumber,
                        uint256 market,
                        BalanceUpdate update,
                        address from
                    );
                
                    event LogWithdraw(
                        address indexed accountOwner,
                        uint256 accountNumber,
                        uint256 market,
                        BalanceUpdate update,
                        address to
                    );
                
                    event LogTransfer(
                        address indexed accountOneOwner,
                        uint256 accountOneNumber,
                        address indexed accountTwoOwner,
                        uint256 accountTwoNumber,
                        uint256 market,
                        BalanceUpdate updateOne,
                        BalanceUpdate updateTwo
                    );
                
                    event LogBuy(
                        address indexed accountOwner,
                        uint256 accountNumber,
                        uint256 takerMarket,
                        uint256 makerMarket,
                        BalanceUpdate takerUpdate,
                        BalanceUpdate makerUpdate,
                        address exchangeWrapper
                    );
                
                    event LogSell(
                        address indexed accountOwner,
                        uint256 accountNumber,
                        uint256 takerMarket,
                        uint256 makerMarket,
                        BalanceUpdate takerUpdate,
                        BalanceUpdate makerUpdate,
                        address exchangeWrapper
                    );
                
                    event LogTrade(
                        address indexed takerAccountOwner,
                        uint256 takerAccountNumber,
                        address indexed makerAccountOwner,
                        uint256 makerAccountNumber,
                        uint256 inputMarket,
                        uint256 outputMarket,
                        BalanceUpdate takerInputUpdate,
                        BalanceUpdate takerOutputUpdate,
                        BalanceUpdate makerInputUpdate,
                        BalanceUpdate makerOutputUpdate,
                        address autoTrader
                    );
                
                    event LogCall(
                        address indexed accountOwner,
                        uint256 accountNumber,
                        address callee
                    );
                
                    event LogLiquidate(
                        address indexed solidAccountOwner,
                        uint256 solidAccountNumber,
                        address indexed liquidAccountOwner,
                        uint256 liquidAccountNumber,
                        uint256 heldMarket,
                        uint256 owedMarket,
                        BalanceUpdate solidHeldUpdate,
                        BalanceUpdate solidOwedUpdate,
                        BalanceUpdate liquidHeldUpdate,
                        BalanceUpdate liquidOwedUpdate
                    );
                
                    event LogVaporize(
                        address indexed solidAccountOwner,
                        uint256 solidAccountNumber,
                        address indexed vaporAccountOwner,
                        uint256 vaporAccountNumber,
                        uint256 heldMarket,
                        uint256 owedMarket,
                        BalanceUpdate solidHeldUpdate,
                        BalanceUpdate solidOwedUpdate,
                        BalanceUpdate vaporOwedUpdate
                    );
                
                    // ============ Structs ============
                
                    struct BalanceUpdate {
                        Types.Wei deltaWei;
                        Types.Par newPar;
                    }
                
                    // ============ Internal Functions ============
                
                    function logIndexUpdate(
                        uint256 marketId,
                        Interest.Index memory index
                    )
                        internal
                    {
                        emit LogIndexUpdate(
                            marketId,
                            index
                        );
                    }
                
                    function logOperation()
                        internal
                    {
                        emit LogOperation(msg.sender);
                    }
                
                    function logDeposit(
                        Storage.State storage state,
                        Actions.DepositArgs memory args,
                        Types.Wei memory deltaWei
                    )
                        internal
                    {
                        emit LogDeposit(
                            args.account.owner,
                            args.account.number,
                            args.market,
                            getBalanceUpdate(
                                state,
                                args.account,
                                args.market,
                                deltaWei
                            ),
                            args.from
                        );
                    }
                
                    function logWithdraw(
                        Storage.State storage state,
                        Actions.WithdrawArgs memory args,
                        Types.Wei memory deltaWei
                    )
                        internal
                    {
                        emit LogWithdraw(
                            args.account.owner,
                            args.account.number,
                            args.market,
                            getBalanceUpdate(
                                state,
                                args.account,
                                args.market,
                                deltaWei
                            ),
                            args.to
                        );
                    }
                
                    function logTransfer(
                        Storage.State storage state,
                        Actions.TransferArgs memory args,
                        Types.Wei memory deltaWei
                    )
                        internal
                    {
                        emit LogTransfer(
                            args.accountOne.owner,
                            args.accountOne.number,
                            args.accountTwo.owner,
                            args.accountTwo.number,
                            args.market,
                            getBalanceUpdate(
                                state,
                                args.accountOne,
                                args.market,
                                deltaWei
                            ),
                            getBalanceUpdate(
                                state,
                                args.accountTwo,
                                args.market,
                                deltaWei.negative()
                            )
                        );
                    }
                
                    function logBuy(
                        Storage.State storage state,
                        Actions.BuyArgs memory args,
                        Types.Wei memory takerWei,
                        Types.Wei memory makerWei
                    )
                        internal
                    {
                        emit LogBuy(
                            args.account.owner,
                            args.account.number,
                            args.takerMarket,
                            args.makerMarket,
                            getBalanceUpdate(
                                state,
                                args.account,
                                args.takerMarket,
                                takerWei
                            ),
                            getBalanceUpdate(
                                state,
                                args.account,
                                args.makerMarket,
                                makerWei
                            ),
                            args.exchangeWrapper
                        );
                    }
                
                    function logSell(
                        Storage.State storage state,
                        Actions.SellArgs memory args,
                        Types.Wei memory takerWei,
                        Types.Wei memory makerWei
                    )
                        internal
                    {
                        emit LogSell(
                            args.account.owner,
                            args.account.number,
                            args.takerMarket,
                            args.makerMarket,
                            getBalanceUpdate(
                                state,
                                args.account,
                                args.takerMarket,
                                takerWei
                            ),
                            getBalanceUpdate(
                                state,
                                args.account,
                                args.makerMarket,
                                makerWei
                            ),
                            args.exchangeWrapper
                        );
                    }
                
                    function logTrade(
                        Storage.State storage state,
                        Actions.TradeArgs memory args,
                        Types.Wei memory inputWei,
                        Types.Wei memory outputWei
                    )
                        internal
                    {
                        BalanceUpdate[4] memory updates = [
                            getBalanceUpdate(
                                state,
                                args.takerAccount,
                                args.inputMarket,
                                inputWei.negative()
                            ),
                            getBalanceUpdate(
                                state,
                                args.takerAccount,
                                args.outputMarket,
                                outputWei.negative()
                            ),
                            getBalanceUpdate(
                                state,
                                args.makerAccount,
                                args.inputMarket,
                                inputWei
                            ),
                            getBalanceUpdate(
                                state,
                                args.makerAccount,
                                args.outputMarket,
                                outputWei
                            )
                        ];
                
                        emit LogTrade(
                            args.takerAccount.owner,
                            args.takerAccount.number,
                            args.makerAccount.owner,
                            args.makerAccount.number,
                            args.inputMarket,
                            args.outputMarket,
                            updates[0],
                            updates[1],
                            updates[2],
                            updates[3],
                            args.autoTrader
                        );
                    }
                
                    function logCall(
                        Actions.CallArgs memory args
                    )
                        internal
                    {
                        emit LogCall(
                            args.account.owner,
                            args.account.number,
                            args.callee
                        );
                    }
                
                    function logLiquidate(
                        Storage.State storage state,
                        Actions.LiquidateArgs memory args,
                        Types.Wei memory heldWei,
                        Types.Wei memory owedWei
                    )
                        internal
                    {
                        BalanceUpdate memory solidHeldUpdate = getBalanceUpdate(
                            state,
                            args.solidAccount,
                            args.heldMarket,
                            heldWei.negative()
                        );
                        BalanceUpdate memory solidOwedUpdate = getBalanceUpdate(
                            state,
                            args.solidAccount,
                            args.owedMarket,
                            owedWei.negative()
                        );
                        BalanceUpdate memory liquidHeldUpdate = getBalanceUpdate(
                            state,
                            args.liquidAccount,
                            args.heldMarket,
                            heldWei
                        );
                        BalanceUpdate memory liquidOwedUpdate = getBalanceUpdate(
                            state,
                            args.liquidAccount,
                            args.owedMarket,
                            owedWei
                        );
                
                        emit LogLiquidate(
                            args.solidAccount.owner,
                            args.solidAccount.number,
                            args.liquidAccount.owner,
                            args.liquidAccount.number,
                            args.heldMarket,
                            args.owedMarket,
                            solidHeldUpdate,
                            solidOwedUpdate,
                            liquidHeldUpdate,
                            liquidOwedUpdate
                        );
                    }
                
                    function logVaporize(
                        Storage.State storage state,
                        Actions.VaporizeArgs memory args,
                        Types.Wei memory heldWei,
                        Types.Wei memory owedWei,
                        Types.Wei memory excessWei
                    )
                        internal
                    {
                        BalanceUpdate memory solidHeldUpdate = getBalanceUpdate(
                            state,
                            args.solidAccount,
                            args.heldMarket,
                            heldWei.negative()
                        );
                        BalanceUpdate memory solidOwedUpdate = getBalanceUpdate(
                            state,
                            args.solidAccount,
                            args.owedMarket,
                            owedWei.negative()
                        );
                        BalanceUpdate memory vaporOwedUpdate = getBalanceUpdate(
                            state,
                            args.vaporAccount,
                            args.owedMarket,
                            owedWei.add(excessWei)
                        );
                
                        emit LogVaporize(
                            args.solidAccount.owner,
                            args.solidAccount.number,
                            args.vaporAccount.owner,
                            args.vaporAccount.number,
                            args.heldMarket,
                            args.owedMarket,
                            solidHeldUpdate,
                            solidOwedUpdate,
                            vaporOwedUpdate
                        );
                    }
                
                    // ============ Private Functions ============
                
                    function getBalanceUpdate(
                        Storage.State storage state,
                        Account.Info memory account,
                        uint256 market,
                        Types.Wei memory deltaWei
                    )
                        private
                        view
                        returns (BalanceUpdate memory)
                    {
                        return BalanceUpdate({
                            deltaWei: deltaWei,
                            newPar: state.getPar(account, market)
                        });
                    }
                }
                
                // File: contracts/protocol/interfaces/IExchangeWrapper.sol
                
                /**
                 * @title IExchangeWrapper
                 * @author dYdX
                 *
                 * Interface that Exchange Wrappers for Solo must implement in order to trade ERC20 tokens.
                 */
                interface IExchangeWrapper {
                
                    // ============ Public Functions ============
                
                    /**
                     * Exchange some amount of takerToken for makerToken.
                     *
                     * @param  tradeOriginator      Address of the initiator of the trade (however, this value
                     *                              cannot always be trusted as it is set at the discretion of the
                     *                              msg.sender)
                     * @param  receiver             Address to set allowance on once the trade has completed
                     * @param  makerToken           Address of makerToken, the token to receive
                     * @param  takerToken           Address of takerToken, the token to pay
                     * @param  requestedFillAmount  Amount of takerToken being paid
                     * @param  orderData            Arbitrary bytes data for any information to pass to the exchange
                     * @return                      The amount of makerToken received
                     */
                    function exchange(
                        address tradeOriginator,
                        address receiver,
                        address makerToken,
                        address takerToken,
                        uint256 requestedFillAmount,
                        bytes calldata orderData
                    )
                        external
                        returns (uint256);
                
                    /**
                     * Get amount of takerToken required to buy a certain amount of makerToken for a given trade.
                     * Should match the takerToken amount used in exchangeForAmount. If the order cannot provide
                     * exactly desiredMakerToken, then it must return the price to buy the minimum amount greater
                     * than desiredMakerToken
                     *
                     * @param  makerToken         Address of makerToken, the token to receive
                     * @param  takerToken         Address of takerToken, the token to pay
                     * @param  desiredMakerToken  Amount of makerToken requested
                     * @param  orderData          Arbitrary bytes data for any information to pass to the exchange
                     * @return                    Amount of takerToken the needed to complete the exchange
                     */
                    function getExchangeCost(
                        address makerToken,
                        address takerToken,
                        uint256 desiredMakerToken,
                        bytes calldata orderData
                    )
                        external
                        view
                        returns (uint256);
                }
                
                // File: contracts/protocol/lib/Exchange.sol
                
                /**
                 * @title Exchange
                 * @author dYdX
                 *
                 * Library for transferring tokens and interacting with ExchangeWrappers by using the Wei struct
                 */
                library Exchange {
                    using Types for Types.Wei;
                
                    // ============ Constants ============
                
                    bytes32 constant FILE = "Exchange";
                
                    // ============ Library Functions ============
                
                    function transferOut(
                        address token,
                        address to,
                        Types.Wei memory deltaWei
                    )
                        internal
                    {
                        Require.that(
                            !deltaWei.isPositive(),
                            FILE,
                            "Cannot transferOut positive",
                            deltaWei.value
                        );
                
                        Token.transfer(
                            token,
                            to,
                            deltaWei.value
                        );
                    }
                
                    function transferIn(
                        address token,
                        address from,
                        Types.Wei memory deltaWei
                    )
                        internal
                    {
                        Require.that(
                            !deltaWei.isNegative(),
                            FILE,
                            "Cannot transferIn negative",
                            deltaWei.value
                        );
                
                        Token.transferFrom(
                            token,
                            from,
                            address(this),
                            deltaWei.value
                        );
                    }
                
                    function getCost(
                        address exchangeWrapper,
                        address supplyToken,
                        address borrowToken,
                        Types.Wei memory desiredAmount,
                        bytes memory orderData
                    )
                        internal
                        view
                        returns (Types.Wei memory)
                    {
                        Require.that(
                            !desiredAmount.isNegative(),
                            FILE,
                            "Cannot getCost negative",
                            desiredAmount.value
                        );
                
                        Types.Wei memory result;
                        result.sign = false;
                        result.value = IExchangeWrapper(exchangeWrapper).getExchangeCost(
                            supplyToken,
                            borrowToken,
                            desiredAmount.value,
                            orderData
                        );
                
                        return result;
                    }
                
                    function exchange(
                        address exchangeWrapper,
                        address accountOwner,
                        address supplyToken,
                        address borrowToken,
                        Types.Wei memory requestedFillAmount,
                        bytes memory orderData
                    )
                        internal
                        returns (Types.Wei memory)
                    {
                        Require.that(
                            !requestedFillAmount.isPositive(),
                            FILE,
                            "Cannot exchange positive",
                            requestedFillAmount.value
                        );
                
                        transferOut(borrowToken, exchangeWrapper, requestedFillAmount);
                
                        Types.Wei memory result;
                        result.sign = true;
                        result.value = IExchangeWrapper(exchangeWrapper).exchange(
                            accountOwner,
                            address(this),
                            supplyToken,
                            borrowToken,
                            requestedFillAmount.value,
                            orderData
                        );
                
                        transferIn(supplyToken, exchangeWrapper, result);
                
                        return result;
                    }
                }
                
                // File: contracts/protocol/impl/OperationImpl.sol
                
                /**
                 * @title OperationImpl
                 * @author dYdX
                 *
                 * Logic for processing actions
                 */
                library OperationImpl {
                    using Cache for Cache.MarketCache;
                    using SafeMath for uint256;
                    using Storage for Storage.State;
                    using Types for Types.Par;
                    using Types for Types.Wei;
                
                    // ============ Constants ============
                
                    bytes32 constant FILE = "OperationImpl";
                
                    // ============ Public Functions ============
                
                    function operate(
                        Storage.State storage state,
                        Account.Info[] memory accounts,
                        Actions.ActionArgs[] memory actions
                    )
                        public
                    {
                        Events.logOperation();
                
                        _verifyInputs(accounts, actions);
                
                        (
                            bool[] memory primaryAccounts,
                            Cache.MarketCache memory cache
                        ) = _runPreprocessing(
                            state,
                            accounts,
                            actions
                        );
                
                        _runActions(
                            state,
                            accounts,
                            actions,
                            cache
                        );
                
                        _verifyFinalState(
                            state,
                            accounts,
                            primaryAccounts,
                            cache
                        );
                    }
                
                    // ============ Helper Functions ============
                
                    function _verifyInputs(
                        Account.Info[] memory accounts,
                        Actions.ActionArgs[] memory actions
                    )
                        private
                        pure
                    {
                        Require.that(
                            actions.length != 0,
                            FILE,
                            "Cannot have zero actions"
                        );
                
                        Require.that(
                            accounts.length != 0,
                            FILE,
                            "Cannot have zero accounts"
                        );
                
                        for (uint256 a = 0; a < accounts.length; a++) {
                            for (uint256 b = a + 1; b < accounts.length; b++) {
                                Require.that(
                                    !Account.equals(accounts[a], accounts[b]),
                                    FILE,
                                    "Cannot duplicate accounts",
                                    a,
                                    b
                                );
                            }
                        }
                    }
                
                    function _runPreprocessing(
                        Storage.State storage state,
                        Account.Info[] memory accounts,
                        Actions.ActionArgs[] memory actions
                    )
                        private
                        returns (
                            bool[] memory,
                            Cache.MarketCache memory
                        )
                    {
                        uint256 numMarkets = state.numMarkets;
                        bool[] memory primaryAccounts = new bool[](accounts.length);
                        Cache.MarketCache memory cache = Cache.create(numMarkets);
                
                        // keep track of primary accounts and indexes that need updating
                        for (uint256 i = 0; i < actions.length; i++) {
                            Actions.ActionArgs memory arg = actions[i];
                            Actions.ActionType actionType = arg.actionType;
                            Actions.MarketLayout marketLayout = Actions.getMarketLayout(actionType);
                            Actions.AccountLayout accountLayout = Actions.getAccountLayout(actionType);
                
                            // parse out primary accounts
                            if (accountLayout != Actions.AccountLayout.OnePrimary) {
                                Require.that(
                                    arg.accountId != arg.otherAccountId,
                                    FILE,
                                    "Duplicate accounts in action",
                                    i
                                );
                                if (accountLayout == Actions.AccountLayout.TwoPrimary) {
                                    primaryAccounts[arg.otherAccountId] = true;
                                } else {
                                    assert(accountLayout == Actions.AccountLayout.PrimaryAndSecondary);
                                    Require.that(
                                        !primaryAccounts[arg.otherAccountId],
                                        FILE,
                                        "Requires non-primary account",
                                        arg.otherAccountId
                                    );
                                }
                            }
                            primaryAccounts[arg.accountId] = true;
                
                            // keep track of indexes to update
                            if (marketLayout == Actions.MarketLayout.OneMarket) {
                                _updateMarket(state, cache, arg.primaryMarketId);
                            } else if (marketLayout == Actions.MarketLayout.TwoMarkets) {
                                Require.that(
                                    arg.primaryMarketId != arg.secondaryMarketId,
                                    FILE,
                                    "Duplicate markets in action",
                                    i
                                );
                                _updateMarket(state, cache, arg.primaryMarketId);
                                _updateMarket(state, cache, arg.secondaryMarketId);
                            } else {
                                assert(marketLayout == Actions.MarketLayout.ZeroMarkets);
                            }
                        }
                
                        // get any other markets for which an account has a balance
                        for (uint256 m = 0; m < numMarkets; m++) {
                            if (cache.hasMarket(m)) {
                                continue;
                            }
                            for (uint256 a = 0; a < accounts.length; a++) {
                                if (!state.getPar(accounts[a], m).isZero()) {
                                    _updateMarket(state, cache, m);
                                    break;
                                }
                            }
                        }
                
                        return (primaryAccounts, cache);
                    }
                
                    function _updateMarket(
                        Storage.State storage state,
                        Cache.MarketCache memory cache,
                        uint256 marketId
                    )
                        private
                    {
                        bool updated = cache.addMarket(state, marketId);
                        if (updated) {
                            Events.logIndexUpdate(marketId, state.updateIndex(marketId));
                        }
                    }
                
                    function _runActions(
                        Storage.State storage state,
                        Account.Info[] memory accounts,
                        Actions.ActionArgs[] memory actions,
                        Cache.MarketCache memory cache
                    )
                        private
                    {
                        for (uint256 i = 0; i < actions.length; i++) {
                            Actions.ActionArgs memory action = actions[i];
                            Actions.ActionType actionType = action.actionType;
                
                            if (actionType == Actions.ActionType.Deposit) {
                                _deposit(state, Actions.parseDepositArgs(accounts, action));
                            }
                            else if (actionType == Actions.ActionType.Withdraw) {
                                _withdraw(state, Actions.parseWithdrawArgs(accounts, action));
                            }
                            else if (actionType == Actions.ActionType.Transfer) {
                                _transfer(state, Actions.parseTransferArgs(accounts, action));
                            }
                            else if (actionType == Actions.ActionType.Buy) {
                                _buy(state, Actions.parseBuyArgs(accounts, action));
                            }
                            else if (actionType == Actions.ActionType.Sell) {
                                _sell(state, Actions.parseSellArgs(accounts, action));
                            }
                            else if (actionType == Actions.ActionType.Trade) {
                                _trade(state, Actions.parseTradeArgs(accounts, action));
                            }
                            else if (actionType == Actions.ActionType.Liquidate) {
                                _liquidate(state, Actions.parseLiquidateArgs(accounts, action), cache);
                            }
                            else if (actionType == Actions.ActionType.Vaporize) {
                                _vaporize(state, Actions.parseVaporizeArgs(accounts, action), cache);
                            }
                            else  {
                                assert(actionType == Actions.ActionType.Call);
                                _call(state, Actions.parseCallArgs(accounts, action));
                            }
                        }
                    }
                
                    function _verifyFinalState(
                        Storage.State storage state,
                        Account.Info[] memory accounts,
                        bool[] memory primaryAccounts,
                        Cache.MarketCache memory cache
                    )
                        private
                    {
                        // verify no increase in borrowPar for closing markets
                        uint256 numMarkets = cache.getNumMarkets();
                        for (uint256 m = 0; m < numMarkets; m++) {
                            if (cache.getIsClosing(m)) {
                                Require.that(
                                    state.getTotalPar(m).borrow <= cache.getBorrowPar(m),
                                    FILE,
                                    "Market is closing",
                                    m
                                );
                            }
                        }
                
                        // verify account collateralization
                        for (uint256 a = 0; a < accounts.length; a++) {
                            Account.Info memory account = accounts[a];
                
                            // validate minBorrowedValue
                            bool collateralized = state.isCollateralized(account, cache, true);
                
                            // don't check collateralization for non-primary accounts
                            if (!primaryAccounts[a]) {
                                continue;
                            }
                
                            // check collateralization for primary accounts
                            Require.that(
                                collateralized,
                                FILE,
                                "Undercollateralized account",
                                account.owner,
                                account.number
                            );
                
                            // ensure status is normal for primary accounts
                            if (state.getStatus(account) != Account.Status.Normal) {
                                state.setStatus(account, Account.Status.Normal);
                            }
                        }
                    }
                
                    // ============ Action Functions ============
                
                    function _deposit(
                        Storage.State storage state,
                        Actions.DepositArgs memory args
                    )
                        private
                    {
                        state.requireIsOperator(args.account, msg.sender);
                
                        Require.that(
                            args.from == msg.sender || args.from == args.account.owner,
                            FILE,
                            "Invalid deposit source",
                            args.from
                        );
                
                        (
                            Types.Par memory newPar,
                            Types.Wei memory deltaWei
                        ) = state.getNewParAndDeltaWei(
                            args.account,
                            args.market,
                            args.amount
                        );
                
                        state.setPar(
                            args.account,
                            args.market,
                            newPar
                        );
                
                        // requires a positive deltaWei
                        Exchange.transferIn(
                            state.getToken(args.market),
                            args.from,
                            deltaWei
                        );
                
                        Events.logDeposit(
                            state,
                            args,
                            deltaWei
                        );
                    }
                
                    function _withdraw(
                        Storage.State storage state,
                        Actions.WithdrawArgs memory args
                    )
                        private
                    {
                        state.requireIsOperator(args.account, msg.sender);
                
                        (
                            Types.Par memory newPar,
                            Types.Wei memory deltaWei
                        ) = state.getNewParAndDeltaWei(
                            args.account,
                            args.market,
                            args.amount
                        );
                
                        state.setPar(
                            args.account,
                            args.market,
                            newPar
                        );
                
                        // requires a negative deltaWei
                        Exchange.transferOut(
                            state.getToken(args.market),
                            args.to,
                            deltaWei
                        );
                
                        Events.logWithdraw(
                            state,
                            args,
                            deltaWei
                        );
                    }
                
                    function _transfer(
                        Storage.State storage state,
                        Actions.TransferArgs memory args
                    )
                        private
                    {
                        state.requireIsOperator(args.accountOne, msg.sender);
                        state.requireIsOperator(args.accountTwo, msg.sender);
                
                        (
                            Types.Par memory newPar,
                            Types.Wei memory deltaWei
                        ) = state.getNewParAndDeltaWei(
                            args.accountOne,
                            args.market,
                            args.amount
                        );
                
                        state.setPar(
                            args.accountOne,
                            args.market,
                            newPar
                        );
                
                        state.setParFromDeltaWei(
                            args.accountTwo,
                            args.market,
                            deltaWei.negative()
                        );
                
                        Events.logTransfer(
                            state,
                            args,
                            deltaWei
                        );
                    }
                
                    function _buy(
                        Storage.State storage state,
                        Actions.BuyArgs memory args
                    )
                        private
                    {
                        state.requireIsOperator(args.account, msg.sender);
                
                        address takerToken = state.getToken(args.takerMarket);
                        address makerToken = state.getToken(args.makerMarket);
                
                        (
                            Types.Par memory makerPar,
                            Types.Wei memory makerWei
                        ) = state.getNewParAndDeltaWei(
                            args.account,
                            args.makerMarket,
                            args.amount
                        );
                
                        Types.Wei memory takerWei = Exchange.getCost(
                            args.exchangeWrapper,
                            makerToken,
                            takerToken,
                            makerWei,
                            args.orderData
                        );
                
                        Types.Wei memory tokensReceived = Exchange.exchange(
                            args.exchangeWrapper,
                            args.account.owner,
                            makerToken,
                            takerToken,
                            takerWei,
                            args.orderData
                        );
                
                        Require.that(
                            tokensReceived.value >= makerWei.value,
                            FILE,
                            "Buy amount less than promised",
                            tokensReceived.value,
                            makerWei.value
                        );
                
                        state.setPar(
                            args.account,
                            args.makerMarket,
                            makerPar
                        );
                
                        state.setParFromDeltaWei(
                            args.account,
                            args.takerMarket,
                            takerWei
                        );
                
                        Events.logBuy(
                            state,
                            args,
                            takerWei,
                            makerWei
                        );
                    }
                
                    function _sell(
                        Storage.State storage state,
                        Actions.SellArgs memory args
                    )
                        private
                    {
                        state.requireIsOperator(args.account, msg.sender);
                
                        address takerToken = state.getToken(args.takerMarket);
                        address makerToken = state.getToken(args.makerMarket);
                
                        (
                            Types.Par memory takerPar,
                            Types.Wei memory takerWei
                        ) = state.getNewParAndDeltaWei(
                            args.account,
                            args.takerMarket,
                            args.amount
                        );
                
                        Types.Wei memory makerWei = Exchange.exchange(
                            args.exchangeWrapper,
                            args.account.owner,
                            makerToken,
                            takerToken,
                            takerWei,
                            args.orderData
                        );
                
                        state.setPar(
                            args.account,
                            args.takerMarket,
                            takerPar
                        );
                
                        state.setParFromDeltaWei(
                            args.account,
                            args.makerMarket,
                            makerWei
                        );
                
                        Events.logSell(
                            state,
                            args,
                            takerWei,
                            makerWei
                        );
                    }
                
                    function _trade(
                        Storage.State storage state,
                        Actions.TradeArgs memory args
                    )
                        private
                    {
                        state.requireIsOperator(args.takerAccount, msg.sender);
                        state.requireIsOperator(args.makerAccount, args.autoTrader);
                
                        Types.Par memory oldInputPar = state.getPar(
                            args.makerAccount,
                            args.inputMarket
                        );
                        (
                            Types.Par memory newInputPar,
                            Types.Wei memory inputWei
                        ) = state.getNewParAndDeltaWei(
                            args.makerAccount,
                            args.inputMarket,
                            args.amount
                        );
                
                        Types.AssetAmount memory outputAmount = IAutoTrader(args.autoTrader).getTradeCost(
                            args.inputMarket,
                            args.outputMarket,
                            args.makerAccount,
                            args.takerAccount,
                            oldInputPar,
                            newInputPar,
                            inputWei,
                            args.tradeData
                        );
                
                        (
                            Types.Par memory newOutputPar,
                            Types.Wei memory outputWei
                        ) = state.getNewParAndDeltaWei(
                            args.makerAccount,
                            args.outputMarket,
                            outputAmount
                        );
                
                        Require.that(
                            outputWei.isZero() || inputWei.isZero() || outputWei.sign != inputWei.sign,
                            FILE,
                            "Trades cannot be one-sided"
                        );
                
                        // set the balance for the maker
                        state.setPar(
                            args.makerAccount,
                            args.inputMarket,
                            newInputPar
                        );
                        state.setPar(
                            args.makerAccount,
                            args.outputMarket,
                            newOutputPar
                        );
                
                        // set the balance for the taker
                        state.setParFromDeltaWei(
                            args.takerAccount,
                            args.inputMarket,
                            inputWei.negative()
                        );
                        state.setParFromDeltaWei(
                            args.takerAccount,
                            args.outputMarket,
                            outputWei.negative()
                        );
                
                        Events.logTrade(
                            state,
                            args,
                            inputWei,
                            outputWei
                        );
                    }
                
                    function _liquidate(
                        Storage.State storage state,
                        Actions.LiquidateArgs memory args,
                        Cache.MarketCache memory cache
                    )
                        private
                    {
                        state.requireIsOperator(args.solidAccount, msg.sender);
                
                        // verify liquidatable
                        if (Account.Status.Liquid != state.getStatus(args.liquidAccount)) {
                            Require.that(
                                !state.isCollateralized(args.liquidAccount, cache, /* requireMinBorrow = */ false),
                                FILE,
                                "Unliquidatable account",
                                args.liquidAccount.owner,
                                args.liquidAccount.number
                            );
                            state.setStatus(args.liquidAccount, Account.Status.Liquid);
                        }
                
                        Types.Wei memory maxHeldWei = state.getWei(
                            args.liquidAccount,
                            args.heldMarket
                        );
                
                        Require.that(
                            !maxHeldWei.isNegative(),
                            FILE,
                            "Collateral cannot be negative",
                            args.liquidAccount.owner,
                            args.liquidAccount.number,
                            args.heldMarket
                        );
                
                        (
                            Types.Par memory owedPar,
                            Types.Wei memory owedWei
                        ) = state.getNewParAndDeltaWeiForLiquidation(
                            args.liquidAccount,
                            args.owedMarket,
                            args.amount
                        );
                
                        (
                            Monetary.Price memory heldPrice,
                            Monetary.Price memory owedPrice
                        ) = _getLiquidationPrices(
                            state,
                            cache,
                            args.heldMarket,
                            args.owedMarket
                        );
                
                        Types.Wei memory heldWei = _owedWeiToHeldWei(owedWei, heldPrice, owedPrice);
                
                        // if attempting to over-borrow the held asset, bound it by the maximum
                        if (heldWei.value > maxHeldWei.value) {
                            heldWei = maxHeldWei.negative();
                            owedWei = _heldWeiToOwedWei(heldWei, heldPrice, owedPrice);
                
                            state.setPar(
                                args.liquidAccount,
                                args.heldMarket,
                                Types.zeroPar()
                            );
                            state.setParFromDeltaWei(
                                args.liquidAccount,
                                args.owedMarket,
                                owedWei
                            );
                        } else {
                            state.setPar(
                                args.liquidAccount,
                                args.owedMarket,
                                owedPar
                            );
                            state.setParFromDeltaWei(
                                args.liquidAccount,
                                args.heldMarket,
                                heldWei
                            );
                        }
                
                        // set the balances for the solid account
                        state.setParFromDeltaWei(
                            args.solidAccount,
                            args.owedMarket,
                            owedWei.negative()
                        );
                        state.setParFromDeltaWei(
                            args.solidAccount,
                            args.heldMarket,
                            heldWei.negative()
                        );
                
                        Events.logLiquidate(
                            state,
                            args,
                            heldWei,
                            owedWei
                        );
                    }
                
                    function _vaporize(
                        Storage.State storage state,
                        Actions.VaporizeArgs memory args,
                        Cache.MarketCache memory cache
                    )
                        private
                    {
                        state.requireIsOperator(args.solidAccount, msg.sender);
                
                        // verify vaporizable
                        if (Account.Status.Vapor != state.getStatus(args.vaporAccount)) {
                            Require.that(
                                state.isVaporizable(args.vaporAccount, cache),
                                FILE,
                                "Unvaporizable account",
                                args.vaporAccount.owner,
                                args.vaporAccount.number
                            );
                            state.setStatus(args.vaporAccount, Account.Status.Vapor);
                        }
                
                        // First, attempt to refund using the same token
                        (
                            bool fullyRepaid,
                            Types.Wei memory excessWei
                        ) = _vaporizeUsingExcess(state, args);
                        if (fullyRepaid) {
                            Events.logVaporize(
                                state,
                                args,
                                Types.zeroWei(),
                                Types.zeroWei(),
                                excessWei
                            );
                            return;
                        }
                
                        Types.Wei memory maxHeldWei = state.getNumExcessTokens(args.heldMarket);
                
                        Require.that(
                            !maxHeldWei.isNegative(),
                            FILE,
                            "Excess cannot be negative",
                            args.heldMarket
                        );
                
                        (
                            Types.Par memory owedPar,
                            Types.Wei memory owedWei
                        ) = state.getNewParAndDeltaWeiForLiquidation(
                            args.vaporAccount,
                            args.owedMarket,
                            args.amount
                        );
                
                        (
                            Monetary.Price memory heldPrice,
                            Monetary.Price memory owedPrice
                        ) = _getLiquidationPrices(
                            state,
                            cache,
                            args.heldMarket,
                            args.owedMarket
                        );
                
                        Types.Wei memory heldWei = _owedWeiToHeldWei(owedWei, heldPrice, owedPrice);
                
                        // if attempting to over-borrow the held asset, bound it by the maximum
                        if (heldWei.value > maxHeldWei.value) {
                            heldWei = maxHeldWei.negative();
                            owedWei = _heldWeiToOwedWei(heldWei, heldPrice, owedPrice);
                
                            state.setParFromDeltaWei(
                                args.vaporAccount,
                                args.owedMarket,
                                owedWei
                            );
                        } else {
                            state.setPar(
                                args.vaporAccount,
                                args.owedMarket,
                                owedPar
                            );
                        }
                
                        // set the balances for the solid account
                        state.setParFromDeltaWei(
                            args.solidAccount,
                            args.owedMarket,
                            owedWei.negative()
                        );
                        state.setParFromDeltaWei(
                            args.solidAccount,
                            args.heldMarket,
                            heldWei.negative()
                        );
                
                        Events.logVaporize(
                            state,
                            args,
                            heldWei,
                            owedWei,
                            excessWei
                        );
                    }
                
                    function _call(
                        Storage.State storage state,
                        Actions.CallArgs memory args
                    )
                        private
                    {
                        state.requireIsOperator(args.account, msg.sender);
                
                        ICallee(args.callee).callFunction(
                            msg.sender,
                            args.account,
                            args.data
                        );
                
                        Events.logCall(args);
                    }
                
                    // ============ Private Functions ============
                
                    /**
                     * For the purposes of liquidation or vaporization, get the value-equivalent amount of heldWei
                     * given owedWei and the (spread-adjusted) prices of each asset.
                     */
                    function _owedWeiToHeldWei(
                        Types.Wei memory owedWei,
                        Monetary.Price memory heldPrice,
                        Monetary.Price memory owedPrice
                    )
                        private
                        pure
                        returns (Types.Wei memory)
                    {
                        return Types.Wei({
                            sign: false,
                            value: Math.getPartial(owedWei.value, owedPrice.value, heldPrice.value)
                        });
                    }
                
                    /**
                     * For the purposes of liquidation or vaporization, get the value-equivalent amount of owedWei
                     * given heldWei and the (spread-adjusted) prices of each asset.
                     */
                    function _heldWeiToOwedWei(
                        Types.Wei memory heldWei,
                        Monetary.Price memory heldPrice,
                        Monetary.Price memory owedPrice
                    )
                        private
                        pure
                        returns (Types.Wei memory)
                    {
                        return Types.Wei({
                            sign: true,
                            value: Math.getPartialRoundUp(heldWei.value, heldPrice.value, owedPrice.value)
                        });
                    }
                
                    /**
                     * Attempt to vaporize an account's balance using the excess tokens in the protocol. Return a
                     * bool and a wei value. The boolean is true if and only if the balance was fully vaporized. The
                     * Wei value is how many excess tokens were used to partially or fully vaporize the account's
                     * negative balance.
                     */
                    function _vaporizeUsingExcess(
                        Storage.State storage state,
                        Actions.VaporizeArgs memory args
                    )
                        internal
                        returns (bool, Types.Wei memory)
                    {
                        Types.Wei memory excessWei = state.getNumExcessTokens(args.owedMarket);
                
                        // There are no excess funds, return zero
                        if (!excessWei.isPositive()) {
                            return (false, Types.zeroWei());
                        }
                
                        Types.Wei memory maxRefundWei = state.getWei(args.vaporAccount, args.owedMarket);
                        maxRefundWei.sign = true;
                
                        // The account is fully vaporizable using excess funds
                        if (excessWei.value >= maxRefundWei.value) {
                            state.setPar(
                                args.vaporAccount,
                                args.owedMarket,
                                Types.zeroPar()
                            );
                            return (true, maxRefundWei);
                        }
                
                        // The account is only partially vaporizable using excess funds
                        else {
                            state.setParFromDeltaWei(
                                args.vaporAccount,
                                args.owedMarket,
                                excessWei
                            );
                            return (false, excessWei);
                        }
                    }
                
                    /**
                     * Return the (spread-adjusted) prices of two assets for the purposes of liquidation or
                     * vaporization.
                     */
                    function _getLiquidationPrices(
                        Storage.State storage state,
                        Cache.MarketCache memory cache,
                        uint256 heldMarketId,
                        uint256 owedMarketId
                    )
                        internal
                        view
                        returns (
                            Monetary.Price memory,
                            Monetary.Price memory
                        )
                    {
                        uint256 originalPrice = cache.getPrice(owedMarketId).value;
                        Decimal.D256 memory spread = state.getLiquidationSpreadForPair(
                            heldMarketId,
                            owedMarketId
                        );
                
                        Monetary.Price memory owedPrice = Monetary.Price({
                            value: originalPrice.add(Decimal.mul(originalPrice, spread))
                        });
                
                        return (cache.getPrice(heldMarketId), owedPrice);
                    }
                }
                
                // File: contracts/protocol/Operation.sol
                
                /**
                 * @title Operation
                 * @author dYdX
                 *
                 * Primary public function for allowing users and contracts to manage accounts within Solo
                 */
                contract Operation is
                    State,
                    ReentrancyGuard
                {
                    // ============ Public Functions ============
                
                    /**
                     * The main entry-point to Solo that allows users and contracts to manage accounts.
                     * Take one or more actions on one or more accounts. The msg.sender must be the owner or
                     * operator of all accounts except for those being liquidated, vaporized, or traded with.
                     * One call to operate() is considered a singular "operation". Account collateralization is
                     * ensured only after the completion of the entire operation.
                     *
                     * @param  accounts  A list of all accounts that will be used in this operation. Cannot contain
                     *                   duplicates. In each action, the relevant account will be referred-to by its
                     *                   index in the list.
                     * @param  actions   An ordered list of all actions that will be taken in this operation. The
                     *                   actions will be processed in order.
                     */
                    function operate(
                        Account.Info[] memory accounts,
                        Actions.ActionArgs[] memory actions
                    )
                        public
                        nonReentrant
                    {
                        OperationImpl.operate(
                            g_state,
                            accounts,
                            actions
                        );
                    }
                }
                
                // File: contracts/protocol/Permission.sol
                
                /**
                 * @title Permission
                 * @author dYdX
                 *
                 * Public function that allows other addresses to manage accounts
                 */
                contract Permission is
                    State
                {
                    // ============ Events ============
                
                    event LogOperatorSet(
                        address indexed owner,
                        address operator,
                        bool trusted
                    );
                
                    // ============ Structs ============
                
                    struct OperatorArg {
                        address operator;
                        bool trusted;
                    }
                
                    // ============ Public Functions ============
                
                    /**
                     * Approves/disapproves any number of operators. An operator is an external address that has the
                     * same permissions to manipulate an account as the owner of the account. Operators are simply
                     * addresses and therefore may either be externally-owned Ethereum accounts OR smart contracts.
                     *
                     * Operators are also able to act as AutoTrader contracts on behalf of the account owner if the
                     * operator is a smart contract and implements the IAutoTrader interface.
                     *
                     * @param  args  A list of OperatorArgs which have an address and a boolean. The boolean value
                     *               denotes whether to approve (true) or revoke approval (false) for that address.
                     */
                    function setOperators(
                        OperatorArg[] memory args
                    )
                        public
                    {
                        for (uint256 i = 0; i < args.length; i++) {
                            address operator = args[i].operator;
                            bool trusted = args[i].trusted;
                            g_state.operators[msg.sender][operator] = trusted;
                            emit LogOperatorSet(msg.sender, operator, trusted);
                        }
                    }
                }
                
                // File: contracts/protocol/SoloMargin.sol
                
                /**
                 * @title SoloMargin
                 * @author dYdX
                 *
                 * Main contract that inherits from other contracts
                 */
                contract SoloMargin is
                    State,
                    Admin,
                    Getters,
                    Operation,
                    Permission
                {
                    // ============ Constructor ============
                
                    constructor(
                        Storage.RiskParams memory riskParams,
                        Storage.RiskLimits memory riskLimits
                    )
                        public
                    {
                        g_state.riskParams = riskParams;
                        g_state.riskLimits = riskLimits;
                    }
                }

                File 2 of 7: CanonicalOrders
                /*
                
                    Copyright 2020 dYdX Trading Inc.
                
                    Licensed under the Apache License, Version 2.0 (the "License");
                    you may not use this file except in compliance with the License.
                    You may obtain a copy of the License at
                
                    http://www.apache.org/licenses/LICENSE-2.0
                
                    Unless required by applicable law or agreed to in writing, software
                    distributed under the License is distributed on an "AS IS" BASIS,
                    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                    See the License for the specific language governing permissions and
                    limitations under the License.
                
                */
                
                pragma solidity 0.5.7;
                pragma experimental ABIEncoderV2;
                
                // File: openzeppelin-solidity/contracts/math/SafeMath.sol
                
                /**
                 * @title SafeMath
                 * @dev Unsigned math operations with safety checks that revert on error
                 */
                library SafeMath {
                    /**
                    * @dev Multiplies two unsigned integers, reverts on overflow.
                    */
                    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
                        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
                        // benefit is lost if 'b' is also tested.
                        // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
                        if (a == 0) {
                            return 0;
                        }
                
                        uint256 c = a * b;
                        require(c / a == b);
                
                        return c;
                    }
                
                    /**
                    * @dev Integer division of two unsigned integers truncating the quotient, reverts on division by zero.
                    */
                    function div(uint256 a, uint256 b) internal pure returns (uint256) {
                        // Solidity only automatically asserts when dividing by 0
                        require(b > 0);
                        uint256 c = a / b;
                        // assert(a == b * c + a % b); // There is no case in which this doesn't hold
                
                        return c;
                    }
                
                    /**
                    * @dev Subtracts two unsigned integers, reverts on overflow (i.e. if subtrahend is greater than minuend).
                    */
                    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                        require(b <= a);
                        uint256 c = a - b;
                
                        return c;
                    }
                
                    /**
                    * @dev Adds two unsigned integers, reverts on overflow.
                    */
                    function add(uint256 a, uint256 b) internal pure returns (uint256) {
                        uint256 c = a + b;
                        require(c >= a);
                
                        return c;
                    }
                
                    /**
                    * @dev Divides two unsigned integers and returns the remainder (unsigned integer modulo),
                    * reverts when dividing by zero.
                    */
                    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
                        require(b != 0);
                        return a % b;
                    }
                }
                
                // File: openzeppelin-solidity/contracts/ownership/Ownable.sol
                
                /**
                 * @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 private _owner;
                
                    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
                
                    /**
                     * @dev The Ownable constructor sets the original `owner` of the contract to the sender
                     * account.
                     */
                    constructor () internal {
                        _owner = msg.sender;
                        emit OwnershipTransferred(address(0), _owner);
                    }
                
                    /**
                     * @return the address of the owner.
                     */
                    function owner() public view returns (address) {
                        return _owner;
                    }
                
                    /**
                     * @dev Throws if called by any account other than the owner.
                     */
                    modifier onlyOwner() {
                        require(isOwner());
                        _;
                    }
                
                    /**
                     * @return true if `msg.sender` is the owner of the contract.
                     */
                    function isOwner() public view returns (bool) {
                        return msg.sender == _owner;
                    }
                
                    /**
                     * @dev Allows the current owner to relinquish control of the contract.
                     * @notice Renouncing to ownership will leave the contract without an owner.
                     * It will not be possible to call the functions with the `onlyOwner`
                     * modifier anymore.
                     */
                    function renounceOwnership() public onlyOwner {
                        emit OwnershipTransferred(_owner, address(0));
                        _owner = address(0);
                    }
                
                    /**
                     * @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 {
                        _transferOwnership(newOwner);
                    }
                
                    /**
                     * @dev Transfers control of the contract to a newOwner.
                     * @param newOwner The address to transfer ownership to.
                     */
                    function _transferOwnership(address newOwner) internal {
                        require(newOwner != address(0));
                        emit OwnershipTransferred(_owner, newOwner);
                        _owner = newOwner;
                    }
                }
                
                // File: contracts/protocol/lib/Require.sol
                
                /**
                 * @title Require
                 * @author dYdX
                 *
                 * Stringifies parameters to pretty-print revert messages. Costs more gas than regular require()
                 */
                library Require {
                
                    // ============ Constants ============
                
                    uint256 constant ASCII_ZERO = 48; // '0'
                    uint256 constant ASCII_RELATIVE_ZERO = 87; // 'a' - 10
                    uint256 constant ASCII_LOWER_EX = 120; // 'x'
                    bytes2 constant COLON = 0x3a20; // ': '
                    bytes2 constant COMMA = 0x2c20; // ', '
                    bytes2 constant LPAREN = 0x203c; // ' <'
                    byte constant RPAREN = 0x3e; // '>'
                    uint256 constant FOUR_BIT_MASK = 0xf;
                
                    // ============ Library Functions ============
                
                    function that(
                        bool must,
                        bytes32 file,
                        bytes32 reason
                    )
                        internal
                        pure
                    {
                        if (!must) {
                            revert(
                                string(
                                    abi.encodePacked(
                                        stringifyTruncated(file),
                                        COLON,
                                        stringifyTruncated(reason)
                                    )
                                )
                            );
                        }
                    }
                
                    function that(
                        bool must,
                        bytes32 file,
                        bytes32 reason,
                        uint256 payloadA
                    )
                        internal
                        pure
                    {
                        if (!must) {
                            revert(
                                string(
                                    abi.encodePacked(
                                        stringifyTruncated(file),
                                        COLON,
                                        stringifyTruncated(reason),
                                        LPAREN,
                                        stringify(payloadA),
                                        RPAREN
                                    )
                                )
                            );
                        }
                    }
                
                    function that(
                        bool must,
                        bytes32 file,
                        bytes32 reason,
                        uint256 payloadA,
                        uint256 payloadB
                    )
                        internal
                        pure
                    {
                        if (!must) {
                            revert(
                                string(
                                    abi.encodePacked(
                                        stringifyTruncated(file),
                                        COLON,
                                        stringifyTruncated(reason),
                                        LPAREN,
                                        stringify(payloadA),
                                        COMMA,
                                        stringify(payloadB),
                                        RPAREN
                                    )
                                )
                            );
                        }
                    }
                
                    function that(
                        bool must,
                        bytes32 file,
                        bytes32 reason,
                        address payloadA
                    )
                        internal
                        pure
                    {
                        if (!must) {
                            revert(
                                string(
                                    abi.encodePacked(
                                        stringifyTruncated(file),
                                        COLON,
                                        stringifyTruncated(reason),
                                        LPAREN,
                                        stringify(payloadA),
                                        RPAREN
                                    )
                                )
                            );
                        }
                    }
                
                    function that(
                        bool must,
                        bytes32 file,
                        bytes32 reason,
                        address payloadA,
                        uint256 payloadB
                    )
                        internal
                        pure
                    {
                        if (!must) {
                            revert(
                                string(
                                    abi.encodePacked(
                                        stringifyTruncated(file),
                                        COLON,
                                        stringifyTruncated(reason),
                                        LPAREN,
                                        stringify(payloadA),
                                        COMMA,
                                        stringify(payloadB),
                                        RPAREN
                                    )
                                )
                            );
                        }
                    }
                
                    function that(
                        bool must,
                        bytes32 file,
                        bytes32 reason,
                        address payloadA,
                        uint256 payloadB,
                        uint256 payloadC
                    )
                        internal
                        pure
                    {
                        if (!must) {
                            revert(
                                string(
                                    abi.encodePacked(
                                        stringifyTruncated(file),
                                        COLON,
                                        stringifyTruncated(reason),
                                        LPAREN,
                                        stringify(payloadA),
                                        COMMA,
                                        stringify(payloadB),
                                        COMMA,
                                        stringify(payloadC),
                                        RPAREN
                                    )
                                )
                            );
                        }
                    }
                
                    function that(
                        bool must,
                        bytes32 file,
                        bytes32 reason,
                        bytes32 payloadA
                    )
                        internal
                        pure
                    {
                        if (!must) {
                            revert(
                                string(
                                    abi.encodePacked(
                                        stringifyTruncated(file),
                                        COLON,
                                        stringifyTruncated(reason),
                                        LPAREN,
                                        stringify(payloadA),
                                        RPAREN
                                    )
                                )
                            );
                        }
                    }
                
                    function that(
                        bool must,
                        bytes32 file,
                        bytes32 reason,
                        bytes32 payloadA,
                        uint256 payloadB,
                        uint256 payloadC
                    )
                        internal
                        pure
                    {
                        if (!must) {
                            revert(
                                string(
                                    abi.encodePacked(
                                        stringifyTruncated(file),
                                        COLON,
                                        stringifyTruncated(reason),
                                        LPAREN,
                                        stringify(payloadA),
                                        COMMA,
                                        stringify(payloadB),
                                        COMMA,
                                        stringify(payloadC),
                                        RPAREN
                                    )
                                )
                            );
                        }
                    }
                
                    // ============ Private Functions ============
                
                    function stringifyTruncated(
                        bytes32 input
                    )
                        private
                        pure
                        returns (bytes memory)
                    {
                        // put the input bytes into the result
                        bytes memory result = abi.encodePacked(input);
                
                        // determine the length of the input by finding the location of the last non-zero byte
                        for (uint256 i = 32; i > 0; ) {
                            // reverse-for-loops with unsigned integer
                            /* solium-disable-next-line security/no-modify-for-iter-var */
                            i--;
                
                            // find the last non-zero byte in order to determine the length
                            if (result[i] != 0) {
                                uint256 length = i + 1;
                
                                /* solium-disable-next-line security/no-inline-assembly */
                                assembly {
                                    mstore(result, length) // r.length = length;
                                }
                
                                return result;
                            }
                        }
                
                        // all bytes are zero
                        return new bytes(0);
                    }
                
                    function stringify(
                        uint256 input
                    )
                        private
                        pure
                        returns (bytes memory)
                    {
                        if (input == 0) {
                            return "0";
                        }
                
                        // get the final string length
                        uint256 j = input;
                        uint256 length;
                        while (j != 0) {
                            length++;
                            j /= 10;
                        }
                
                        // allocate the string
                        bytes memory bstr = new bytes(length);
                
                        // populate the string starting with the least-significant character
                        j = input;
                        for (uint256 i = length; i > 0; ) {
                            // reverse-for-loops with unsigned integer
                            /* solium-disable-next-line security/no-modify-for-iter-var */
                            i--;
                
                            // take last decimal digit
                            bstr[i] = byte(uint8(ASCII_ZERO + (j % 10)));
                
                            // remove the last decimal digit
                            j /= 10;
                        }
                
                        return bstr;
                    }
                
                    function stringify(
                        address input
                    )
                        private
                        pure
                        returns (bytes memory)
                    {
                        uint256 z = uint256(input);
                
                        // addresses are "0x" followed by 20 bytes of data which take up 2 characters each
                        bytes memory result = new bytes(42);
                
                        // populate the result with "0x"
                        result[0] = byte(uint8(ASCII_ZERO));
                        result[1] = byte(uint8(ASCII_LOWER_EX));
                
                        // for each byte (starting from the lowest byte), populate the result with two characters
                        for (uint256 i = 0; i < 20; i++) {
                            // each byte takes two characters
                            uint256 shift = i * 2;
                
                            // populate the least-significant character
                            result[41 - shift] = char(z & FOUR_BIT_MASK);
                            z = z >> 4;
                
                            // populate the most-significant character
                            result[40 - shift] = char(z & FOUR_BIT_MASK);
                            z = z >> 4;
                        }
                
                        return result;
                    }
                
                    function stringify(
                        bytes32 input
                    )
                        private
                        pure
                        returns (bytes memory)
                    {
                        uint256 z = uint256(input);
                
                        // bytes32 are "0x" followed by 32 bytes of data which take up 2 characters each
                        bytes memory result = new bytes(66);
                
                        // populate the result with "0x"
                        result[0] = byte(uint8(ASCII_ZERO));
                        result[1] = byte(uint8(ASCII_LOWER_EX));
                
                        // for each byte (starting from the lowest byte), populate the result with two characters
                        for (uint256 i = 0; i < 32; i++) {
                            // each byte takes two characters
                            uint256 shift = i * 2;
                
                            // populate the least-significant character
                            result[65 - shift] = char(z & FOUR_BIT_MASK);
                            z = z >> 4;
                
                            // populate the most-significant character
                            result[64 - shift] = char(z & FOUR_BIT_MASK);
                            z = z >> 4;
                        }
                
                        return result;
                    }
                
                    function char(
                        uint256 input
                    )
                        private
                        pure
                        returns (byte)
                    {
                        // return ASCII digit (0-9)
                        if (input < 10) {
                            return byte(uint8(input + ASCII_ZERO));
                        }
                
                        // return ASCII letter (a-f)
                        return byte(uint8(input + ASCII_RELATIVE_ZERO));
                    }
                }
                
                // File: contracts/protocol/lib/Math.sol
                
                /**
                 * @title Math
                 * @author dYdX
                 *
                 * Library for non-standard Math functions
                 */
                library Math {
                    using SafeMath for uint256;
                
                    // ============ Constants ============
                
                    bytes32 constant FILE = "Math";
                
                    // ============ Library Functions ============
                
                    /*
                     * Return target * (numerator / denominator).
                     */
                    function getPartial(
                        uint256 target,
                        uint256 numerator,
                        uint256 denominator
                    )
                        internal
                        pure
                        returns (uint256)
                    {
                        return target.mul(numerator).div(denominator);
                    }
                
                    /*
                     * Return target * (numerator / denominator), but rounded up.
                     */
                    function getPartialRoundUp(
                        uint256 target,
                        uint256 numerator,
                        uint256 denominator
                    )
                        internal
                        pure
                        returns (uint256)
                    {
                        if (target == 0 || numerator == 0) {
                            // SafeMath will check for zero denominator
                            return SafeMath.div(0, denominator);
                        }
                        return target.mul(numerator).sub(1).div(denominator).add(1);
                    }
                
                    function to128(
                        uint256 number
                    )
                        internal
                        pure
                        returns (uint128)
                    {
                        uint128 result = uint128(number);
                        Require.that(
                            result == number,
                            FILE,
                            "Unsafe cast to uint128"
                        );
                        return result;
                    }
                
                    function to96(
                        uint256 number
                    )
                        internal
                        pure
                        returns (uint96)
                    {
                        uint96 result = uint96(number);
                        Require.that(
                            result == number,
                            FILE,
                            "Unsafe cast to uint96"
                        );
                        return result;
                    }
                
                    function to32(
                        uint256 number
                    )
                        internal
                        pure
                        returns (uint32)
                    {
                        uint32 result = uint32(number);
                        Require.that(
                            result == number,
                            FILE,
                            "Unsafe cast to uint32"
                        );
                        return result;
                    }
                
                    function min(
                        uint256 a,
                        uint256 b
                    )
                        internal
                        pure
                        returns (uint256)
                    {
                        return a < b ? a : b;
                    }
                
                    function max(
                        uint256 a,
                        uint256 b
                    )
                        internal
                        pure
                        returns (uint256)
                    {
                        return a > b ? a : b;
                    }
                }
                
                // File: contracts/protocol/lib/Monetary.sol
                
                /**
                 * @title Monetary
                 * @author dYdX
                 *
                 * Library for types involving money
                 */
                library Monetary {
                
                    /*
                     * The price of a base-unit of an asset.
                     */
                    struct Price {
                        uint256 value;
                    }
                
                    /*
                     * Total value of an some amount of an asset. Equal to (price * amount).
                     */
                    struct Value {
                        uint256 value;
                    }
                }
                
                // File: contracts/protocol/lib/Types.sol
                
                /**
                 * @title Types
                 * @author dYdX
                 *
                 * Library for interacting with the basic structs used in Solo
                 */
                library Types {
                    using Math for uint256;
                
                    // ============ AssetAmount ============
                
                    enum AssetDenomination {
                        Wei, // the amount is denominated in wei
                        Par  // the amount is denominated in par
                    }
                
                    enum AssetReference {
                        Delta, // the amount is given as a delta from the current value
                        Target // the amount is given as an exact number to end up at
                    }
                
                    struct AssetAmount {
                        bool sign; // true if positive
                        AssetDenomination denomination;
                        AssetReference ref;
                        uint256 value;
                    }
                
                    // ============ Par (Principal Amount) ============
                
                    // Total borrow and supply values for a market
                    struct TotalPar {
                        uint128 borrow;
                        uint128 supply;
                    }
                
                    // Individual principal amount for an account
                    struct Par {
                        bool sign; // true if positive
                        uint128 value;
                    }
                
                    function zeroPar()
                        internal
                        pure
                        returns (Par memory)
                    {
                        return Par({
                            sign: false,
                            value: 0
                        });
                    }
                
                    function sub(
                        Par memory a,
                        Par memory b
                    )
                        internal
                        pure
                        returns (Par memory)
                    {
                        return add(a, negative(b));
                    }
                
                    function add(
                        Par memory a,
                        Par memory b
                    )
                        internal
                        pure
                        returns (Par memory)
                    {
                        Par memory result;
                        if (a.sign == b.sign) {
                            result.sign = a.sign;
                            result.value = SafeMath.add(a.value, b.value).to128();
                        } else {
                            if (a.value >= b.value) {
                                result.sign = a.sign;
                                result.value = SafeMath.sub(a.value, b.value).to128();
                            } else {
                                result.sign = b.sign;
                                result.value = SafeMath.sub(b.value, a.value).to128();
                            }
                        }
                        return result;
                    }
                
                    function equals(
                        Par memory a,
                        Par memory b
                    )
                        internal
                        pure
                        returns (bool)
                    {
                        if (a.value == b.value) {
                            if (a.value == 0) {
                                return true;
                            }
                            return a.sign == b.sign;
                        }
                        return false;
                    }
                
                    function negative(
                        Par memory a
                    )
                        internal
                        pure
                        returns (Par memory)
                    {
                        return Par({
                            sign: !a.sign,
                            value: a.value
                        });
                    }
                
                    function isNegative(
                        Par memory a
                    )
                        internal
                        pure
                        returns (bool)
                    {
                        return !a.sign && a.value > 0;
                    }
                
                    function isPositive(
                        Par memory a
                    )
                        internal
                        pure
                        returns (bool)
                    {
                        return a.sign && a.value > 0;
                    }
                
                    function isZero(
                        Par memory a
                    )
                        internal
                        pure
                        returns (bool)
                    {
                        return a.value == 0;
                    }
                
                    // ============ Wei (Token Amount) ============
                
                    // Individual token amount for an account
                    struct Wei {
                        bool sign; // true if positive
                        uint256 value;
                    }
                
                    function zeroWei()
                        internal
                        pure
                        returns (Wei memory)
                    {
                        return Wei({
                            sign: false,
                            value: 0
                        });
                    }
                
                    function sub(
                        Wei memory a,
                        Wei memory b
                    )
                        internal
                        pure
                        returns (Wei memory)
                    {
                        return add(a, negative(b));
                    }
                
                    function add(
                        Wei memory a,
                        Wei memory b
                    )
                        internal
                        pure
                        returns (Wei memory)
                    {
                        Wei memory result;
                        if (a.sign == b.sign) {
                            result.sign = a.sign;
                            result.value = SafeMath.add(a.value, b.value);
                        } else {
                            if (a.value >= b.value) {
                                result.sign = a.sign;
                                result.value = SafeMath.sub(a.value, b.value);
                            } else {
                                result.sign = b.sign;
                                result.value = SafeMath.sub(b.value, a.value);
                            }
                        }
                        return result;
                    }
                
                    function equals(
                        Wei memory a,
                        Wei memory b
                    )
                        internal
                        pure
                        returns (bool)
                    {
                        if (a.value == b.value) {
                            if (a.value == 0) {
                                return true;
                            }
                            return a.sign == b.sign;
                        }
                        return false;
                    }
                
                    function negative(
                        Wei memory a
                    )
                        internal
                        pure
                        returns (Wei memory)
                    {
                        return Wei({
                            sign: !a.sign,
                            value: a.value
                        });
                    }
                
                    function isNegative(
                        Wei memory a
                    )
                        internal
                        pure
                        returns (bool)
                    {
                        return !a.sign && a.value > 0;
                    }
                
                    function isPositive(
                        Wei memory a
                    )
                        internal
                        pure
                        returns (bool)
                    {
                        return a.sign && a.value > 0;
                    }
                
                    function isZero(
                        Wei memory a
                    )
                        internal
                        pure
                        returns (bool)
                    {
                        return a.value == 0;
                    }
                }
                
                // File: contracts/protocol/lib/Account.sol
                
                /**
                 * @title Account
                 * @author dYdX
                 *
                 * Library of structs and functions that represent an account
                 */
                library Account {
                    // ============ Enums ============
                
                    /*
                     * Most-recently-cached account status.
                     *
                     * Normal: Can only be liquidated if the account values are violating the global margin-ratio.
                     * Liquid: Can be liquidated no matter the account values.
                     *         Can be vaporized if there are no more positive account values.
                     * Vapor:  Has only negative (or zeroed) account values. Can be vaporized.
                     *
                     */
                    enum Status {
                        Normal,
                        Liquid,
                        Vapor
                    }
                
                    // ============ Structs ============
                
                    // Represents the unique key that specifies an account
                    struct Info {
                        address owner;  // The address that owns the account
                        uint256 number; // A nonce that allows a single address to control many accounts
                    }
                
                    // The complete storage for any account
                    struct Storage {
                        mapping (uint256 => Types.Par) balances; // Mapping from marketId to principal
                        Status status;
                    }
                
                    // ============ Library Functions ============
                
                    function equals(
                        Info memory a,
                        Info memory b
                    )
                        internal
                        pure
                        returns (bool)
                    {
                        return a.owner == b.owner && a.number == b.number;
                    }
                }
                
                // File: contracts/protocol/interfaces/IAutoTrader.sol
                
                /**
                 * @title IAutoTrader
                 * @author dYdX
                 *
                 * Interface that Auto-Traders for Solo must implement in order to approve trades.
                 */
                contract IAutoTrader {
                
                    // ============ Public Functions ============
                
                    /**
                     * Allows traders to make trades approved by this smart contract. The active trader's account is
                     * the takerAccount and the passive account (for which this contract approves trades
                     * on-behalf-of) is the makerAccount.
                     *
                     * @param  inputMarketId   The market for which the trader specified the original amount
                     * @param  outputMarketId  The market for which the trader wants the resulting amount specified
                     * @param  makerAccount    The account for which this contract is making trades
                     * @param  takerAccount    The account requesting the trade
                     * @param  oldInputPar     The old principal amount for the makerAccount for the inputMarketId
                     * @param  newInputPar     The new principal amount for the makerAccount for the inputMarketId
                     * @param  inputWei        The change in token amount for the makerAccount for the inputMarketId
                     * @param  data            Arbitrary data passed in by the trader
                     * @return                 The AssetAmount for the makerAccount for the outputMarketId
                     */
                    function getTradeCost(
                        uint256 inputMarketId,
                        uint256 outputMarketId,
                        Account.Info memory makerAccount,
                        Account.Info memory takerAccount,
                        Types.Par memory oldInputPar,
                        Types.Par memory newInputPar,
                        Types.Wei memory inputWei,
                        bytes memory data
                    )
                        public
                        returns (Types.AssetAmount memory);
                }
                
                // File: contracts/protocol/interfaces/ICallee.sol
                
                /**
                 * @title ICallee
                 * @author dYdX
                 *
                 * Interface that Callees for Solo must implement in order to ingest data.
                 */
                contract ICallee {
                
                    // ============ Public Functions ============
                
                    /**
                     * Allows users to send this contract arbitrary data.
                     *
                     * @param  sender       The msg.sender to Solo
                     * @param  accountInfo  The account from which the data is being sent
                     * @param  data         Arbitrary data given by the sender
                     */
                    function callFunction(
                        address sender,
                        Account.Info memory accountInfo,
                        bytes memory data
                    )
                        public;
                }
                
                // File: contracts/protocol/SoloMargin.sol
                
                contract SoloMargin {
                
                    /* ... */
                
                    function getAccountWei(
                        Account.Info memory account,
                        uint256 marketId
                    )
                        public
                        view
                        returns (Types.Wei memory);
                
                    function getMarketPrice(
                        uint256 marketId
                    )
                        public
                        view
                        returns (Monetary.Price memory);
                
                    /* ... */
                }
                
                // File: contracts/external/helpers/OnlySolo.sol
                
                /**
                 * @title OnlySolo
                 * @author dYdX
                 *
                 * Inheritable contract that restricts the calling of certain functions to Solo only
                 */
                contract OnlySolo {
                
                    // ============ Constants ============
                
                    bytes32 constant FILE = "OnlySolo";
                
                    // ============ Storage ============
                
                    SoloMargin public SOLO_MARGIN;
                
                    // ============ Constructor ============
                
                    constructor (
                        address soloMargin
                    )
                        public
                    {
                        SOLO_MARGIN = SoloMargin(soloMargin);
                    }
                
                    // ============ Modifiers ============
                
                    modifier onlySolo(address from) {
                        Require.that(
                            from == address(SOLO_MARGIN),
                            FILE,
                            "Only Solo can call function",
                            from
                        );
                        _;
                    }
                }
                
                // File: contracts/external/lib/TypedSignature.sol
                
                /**
                 * @title TypedSignature
                 * @author dYdX
                 *
                 * Library to unparse typed signatures
                 */
                library TypedSignature {
                
                    // ============ Constants ============
                
                    bytes32 constant private FILE = "TypedSignature";
                
                    // prepended message with the length of the signed hash in decimal
                    bytes constant private PREPEND_DEC = "\x19Ethereum Signed Message:\n32";
                
                    // prepended message with the length of the signed hash in hexadecimal
                    bytes constant private PREPEND_HEX = "\x19Ethereum Signed Message:\n\x20";
                
                    // Number of bytes in a typed signature
                    uint256 constant private NUM_SIGNATURE_BYTES = 66;
                
                    // ============ Enums ============
                
                    // Different RPC providers may implement signing methods differently, so we allow different
                    // signature types depending on the string prepended to a hash before it was signed.
                    enum SignatureType {
                        NoPrepend,   // No string was prepended.
                        Decimal,     // PREPEND_DEC was prepended.
                        Hexadecimal, // PREPEND_HEX was prepended.
                        Invalid      // Not a valid type. Used for bound-checking.
                    }
                
                    // ============ Functions ============
                
                    /**
                     * Gives the address of the signer of a hash. Also allows for the commonly prepended string of
                     * '\x19Ethereum Signed Message:\n' + message.length
                     *
                     * @param  hash               Hash that was signed (does not include prepended message)
                     * @param  signatureWithType  Type and ECDSA signature with structure: {32:r}{32:s}{1:v}{1:type}
                     * @return                    address of the signer of the hash
                     */
                    function recover(
                        bytes32 hash,
                        bytes memory signatureWithType
                    )
                        internal
                        pure
                        returns (address)
                    {
                        Require.that(
                            signatureWithType.length == NUM_SIGNATURE_BYTES,
                            FILE,
                            "Invalid signature length"
                        );
                
                        bytes32 r;
                        bytes32 s;
                        uint8 v;
                        uint8 rawSigType;
                
                        /* solium-disable-next-line security/no-inline-assembly */
                        assembly {
                            r := mload(add(signatureWithType, 0x20))
                            s := mload(add(signatureWithType, 0x40))
                            let lastSlot := mload(add(signatureWithType, 0x60))
                            v := byte(0, lastSlot)
                            rawSigType := byte(1, lastSlot)
                        }
                
                        Require.that(
                            rawSigType < uint8(SignatureType.Invalid),
                            FILE,
                            "Invalid signature type"
                        );
                
                        SignatureType sigType = SignatureType(rawSigType);
                
                        bytes32 signedHash;
                        if (sigType == SignatureType.NoPrepend) {
                            signedHash = hash;
                        } else if (sigType == SignatureType.Decimal) {
                            signedHash = keccak256(abi.encodePacked(PREPEND_DEC, hash));
                        } else {
                            assert(sigType == SignatureType.Hexadecimal);
                            signedHash = keccak256(abi.encodePacked(PREPEND_HEX, hash));
                        }
                
                        return ecrecover(
                            signedHash,
                            v,
                            r,
                            s
                        );
                    }
                }
                
                // File: contracts/external/traders/CanonicalOrders.sol
                
                /**
                 * @title CanonicalOrders
                 * @author dYdX
                 *
                 * Allows for Canonical Orders to be used with dYdX
                 */
                contract CanonicalOrders is
                    Ownable,
                    OnlySolo,
                    IAutoTrader,
                    ICallee
                {
                    using Math for uint256;
                    using SafeMath for uint256;
                    using Types for Types.Par;
                    using Types for Types.Wei;
                
                    // ============ Constants ============
                
                    bytes32 constant private FILE = "CanonicalOrders";
                
                    // EIP191 header for EIP712 prefix
                    bytes2 constant private EIP191_HEADER = 0x1901;
                
                    // EIP712 Domain Name value
                    string constant private EIP712_DOMAIN_NAME = "CanonicalOrders";
                
                    // EIP712 Domain Version value
                    string constant private EIP712_DOMAIN_VERSION = "1.1";
                
                    // Hash of the EIP712 Domain Separator Schema
                    /* solium-disable-next-line indentation */
                    bytes32 constant private EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH = keccak256(abi.encodePacked(
                        "EIP712Domain(",
                        "string name,",
                        "string version,",
                        "uint256 chainId,",
                        "address verifyingContract",
                        ")"
                    ));
                
                    // Hash of the EIP712 CanonicalOrder struct
                    /* solium-disable-next-line indentation */
                    bytes32 constant private EIP712_ORDER_STRUCT_SCHEMA_HASH = keccak256(abi.encodePacked(
                        "CanonicalOrder(",
                        "bytes32 flags,",
                        "uint256 baseMarket,",
                        "uint256 quoteMarket,",
                        "uint256 amount,",
                        "uint256 limitPrice,",
                        "uint256 triggerPrice,",
                        "uint256 limitFee,",
                        "address makerAccountOwner,",
                        "uint256 makerAccountNumber,",
                        "uint256 expiration",
                        ")"
                    ));
                
                    // Number of bytes in an Order struct plus number of bytes in a FillArgs struct
                    uint256 constant private NUM_ORDER_AND_FILL_BYTES = 416;
                
                    // Number of bytes in a typed signature
                    uint256 constant private NUM_SIGNATURE_BYTES = 66;
                
                    // The number of decimal places of precision in the price ratio of a triggerPrice
                    uint256 constant private PRICE_BASE = 10 ** 18;
                
                    // Bitmasks for the order.flag argument
                    bytes32 constant private IS_BUY_FLAG = bytes32(uint256(1));
                    bytes32 constant private IS_DECREASE_ONLY_FLAG = bytes32(uint256(1 << 1));
                    bytes32 constant private IS_NEGATIVE_FEE_FLAG = bytes32(uint256(1 << 2));
                
                    // ============ Enums ============
                
                    enum OrderStatus {
                        Null,
                        Approved,
                        Canceled
                    }
                
                    enum CallFunctionType {
                        Approve,
                        Cancel,
                        SetFillArgs
                    }
                
                    // ============ Structs ============
                
                    struct Order {
                        bytes32 flags; // salt, negativeFee, decreaseOnly, isBuy
                        uint256 baseMarket;
                        uint256 quoteMarket;
                        uint256 amount;
                        uint256 limitPrice;
                        uint256 triggerPrice;
                        uint256 limitFee;
                        address makerAccountOwner;
                        uint256 makerAccountNumber;
                        uint256 expiration;
                    }
                
                    struct FillArgs {
                        uint256 price;
                        uint128 fee;
                        bool isNegativeFee;
                    }
                
                    struct OrderInfo {
                        Order order;
                        FillArgs fill;
                        bytes32 orderHash;
                    }
                
                    struct OrderQueryOutput {
                        OrderStatus orderStatus;
                        uint256 filledAmount;
                    }
                
                    // ============ Events ============
                
                    event LogContractStatusSet(
                        bool operational
                    );
                
                    event LogTakerSet(
                        address taker
                    );
                
                    event LogCanonicalOrderCanceled(
                        bytes32 indexed orderHash,
                        address indexed canceler,
                        uint256 baseMarket,
                        uint256 quoteMarket
                    );
                
                    event LogCanonicalOrderApproved(
                        bytes32 indexed orderHash,
                        address indexed approver,
                        uint256 baseMarket,
                        uint256 quoteMarket
                    );
                
                    event LogCanonicalOrderFilled(
                        bytes32 indexed orderHash,
                        address indexed orderMaker,
                        uint256 fillAmount,
                        uint256 triggerPrice,
                        bytes32 orderFlags,
                        FillArgs fill
                    );
                
                    // ============ Immutable Storage ============
                
                    // Hash of the EIP712 Domain Separator data
                    bytes32 public EIP712_DOMAIN_HASH;
                
                    // ============ Mutable Storage ============
                
                    // true if this contract can process orders
                    bool public g_isOperational;
                
                    // order hash => filled amount (in baseAmount)
                    mapping (bytes32 => uint256) public g_filledAmount;
                
                    // order hash => status
                    mapping (bytes32 => OrderStatus) public g_status;
                
                    // stored fillArgs
                    FillArgs public g_fillArgs;
                
                    // required taker address
                    address public g_taker;
                
                    // ============ Constructor ============
                
                    constructor (
                        address soloMargin,
                        address taker,
                        uint256 chainId
                    )
                        public
                        OnlySolo(soloMargin)
                    {
                        g_isOperational = true;
                        g_taker = taker;
                
                        /* solium-disable-next-line indentation */
                        EIP712_DOMAIN_HASH = keccak256(abi.encode(
                            EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH,
                            keccak256(bytes(EIP712_DOMAIN_NAME)),
                            keccak256(bytes(EIP712_DOMAIN_VERSION)),
                            chainId,
                            address(this)
                        ));
                    }
                
                    // ============ Admin Functions ============
                
                    /**
                     * The owner can shut down the exchange.
                     */
                    function shutDown()
                        external
                        onlyOwner
                    {
                        g_isOperational = false;
                        emit LogContractStatusSet(false);
                    }
                
                    /**
                     * The owner can start back up the exchange.
                     */
                    function startUp()
                        external
                        onlyOwner
                    {
                        g_isOperational = true;
                        emit LogContractStatusSet(true);
                    }
                
                    /**
                     * The owner can set the taker address.
                     */
                    function setTakerAddress(
                        address taker
                    )
                        external
                        onlyOwner
                    {
                        g_taker = taker;
                        emit LogTakerSet(taker);
                    }
                
                    // ============ External Functions ============
                
                    /**
                     * Cancels an order.
                     *
                     * @param  order  The order to cancel
                     */
                    function cancelOrder(
                        Order memory order
                    )
                        public
                    {
                        cancelOrderInternal(msg.sender, order);
                    }
                
                    /**
                     * Approves an order. Cannot already be canceled.
                     *
                     * @param  order  The order to approve
                     */
                    function approveOrder(
                        Order memory order
                    )
                        public
                    {
                        approveOrderInternal(msg.sender, order);
                    }
                
                    // ============ Only-Solo Functions ============
                
                    /**
                     * Allows traders to make trades approved by this smart contract. The active trader's account is
                     * the takerAccount and the passive account (for which this contract approves trades
                     * on-behalf-of) is the makerAccount.
                     *
                     * @param  inputMarketId   The market for which the trader specified the original amount
                     * @param  outputMarketId  The market for which the trader wants the resulting amount specified
                     * @param  makerAccount    The account for which this contract is making trades
                     * @param  takerAccount    The account requesting the trade
                     * @param  oldInputPar     The par balance of the makerAccount for inputMarketId pre-trade
                     * @param  newInputPar     The par balance of the makerAccount for inputMarketId post-trade
                     * @param  inputWei        The change in token amount for the makerAccount for the inputMarketId
                     * @param  data            Arbitrary data passed in by the trader
                     * @return                 The AssetAmount for the makerAccount for the outputMarketId
                     */
                    function getTradeCost(
                        uint256 inputMarketId,
                        uint256 outputMarketId,
                        Account.Info memory makerAccount,
                        Account.Info memory takerAccount,
                        Types.Par memory oldInputPar,
                        Types.Par memory newInputPar,
                        Types.Wei memory inputWei,
                        bytes memory data
                    )
                        public
                        onlySolo(msg.sender)
                        returns (Types.AssetAmount memory)
                    {
                        Require.that(
                            g_isOperational,
                            FILE,
                            "Contract is not operational"
                        );
                
                        OrderInfo memory orderInfo = getOrderInfo(data);
                
                        verifySignature(orderInfo, data);
                
                        verifyOrderInfo(
                            orderInfo,
                            makerAccount,
                            takerAccount,
                            inputMarketId,
                            outputMarketId,
                            inputWei
                        );
                
                        Types.AssetAmount memory assetAmount = getOutputAssetAmount(
                            inputMarketId,
                            outputMarketId,
                            inputWei,
                            orderInfo
                        );
                
                        if (isDecreaseOnly(orderInfo.order)) {
                            verifyDecreaseOnly(
                                oldInputPar,
                                newInputPar,
                                assetAmount,
                                makerAccount,
                                outputMarketId
                            );
                        }
                
                        return assetAmount;
                    }
                
                    /**
                     * Allows users to send this contract arbitrary data.
                     *
                     *  param  sender       (unused)
                     * @param  accountInfo  The account from which the data is being sent
                     * @param  data         Arbitrary data given by the sender
                     */
                    function callFunction(
                        address /* sender */,
                        Account.Info memory accountInfo,
                        bytes memory data
                    )
                        public
                        onlySolo(msg.sender)
                    {
                        CallFunctionType cft = abi.decode(data, (CallFunctionType));
                
                        if (cft == CallFunctionType.SetFillArgs) {
                            FillArgs memory fillArgs;
                            (cft, fillArgs) = abi.decode(data, (CallFunctionType, FillArgs));
                            g_fillArgs = fillArgs;
                        } else {
                            Order memory order;
                            (cft, order) = abi.decode(data, (CallFunctionType, Order));
                            if (cft == CallFunctionType.Approve) {
                                approveOrderInternal(accountInfo.owner, order);
                            } else {
                                assert(cft == CallFunctionType.Cancel);
                                cancelOrderInternal(accountInfo.owner, order);
                            }
                        }
                    }
                
                    // ============ Getters ============
                
                    /**
                     * Returns the status and the filled amount of several orders.
                     */
                    function getOrderStates(
                        bytes32[] memory orderHashes
                    )
                        public
                        view
                        returns(OrderQueryOutput[] memory)
                    {
                        uint256 numOrders = orderHashes.length;
                        OrderQueryOutput[] memory output = new OrderQueryOutput[](numOrders);
                
                        // for each order
                        for (uint256 i = 0; i < numOrders; i++) {
                            bytes32 orderHash = orderHashes[i];
                            output[i] = OrderQueryOutput({
                                orderStatus: g_status[orderHash],
                                filledAmount: g_filledAmount[orderHash]
                            });
                        }
                        return output;
                    }
                
                    // ============ Private Storage Functions ============
                
                    /**
                     * Cancels an order as long as it is not already canceled.
                     */
                    function cancelOrderInternal(
                        address canceler,
                        Order memory order
                    )
                        private
                    {
                        Require.that(
                            canceler == order.makerAccountOwner,
                            FILE,
                            "Canceler must be maker"
                        );
                        bytes32 orderHash = getOrderHash(order);
                        g_status[orderHash] = OrderStatus.Canceled;
                        emit LogCanonicalOrderCanceled(
                            orderHash,
                            canceler,
                            order.baseMarket,
                            order.quoteMarket
                        );
                    }
                
                    /**
                     * Approves an order as long as it is not already approved or canceled.
                     */
                    function approveOrderInternal(
                        address approver,
                        Order memory order
                    )
                        private
                    {
                        Require.that(
                            approver == order.makerAccountOwner,
                            FILE,
                            "Approver must be maker"
                        );
                        bytes32 orderHash = getOrderHash(order);
                        Require.that(
                            g_status[orderHash] != OrderStatus.Canceled,
                            FILE,
                            "Cannot approve canceled order",
                            orderHash
                        );
                        g_status[orderHash] = OrderStatus.Approved;
                        emit LogCanonicalOrderApproved(
                            orderHash,
                            approver,
                            order.baseMarket,
                            order.quoteMarket
                        );
                    }
                
                    // ============ Private Helper Functions ============
                
                    /**
                     * Parses the order, verifies that it is not expired or canceled, and verifies the signature.
                     */
                    function getOrderInfo(
                        bytes memory data
                    )
                        private
                        returns (OrderInfo memory)
                    {
                        Require.that(
                            (
                                data.length == NUM_ORDER_AND_FILL_BYTES ||
                                data.length == NUM_ORDER_AND_FILL_BYTES + NUM_SIGNATURE_BYTES
                            ),
                            FILE,
                            "Cannot parse order from data"
                        );
                
                        // load orderInfo from calldata
                        OrderInfo memory orderInfo;
                        (
                            orderInfo.order,
                            orderInfo.fill
                        ) = abi.decode(data, (Order, FillArgs));
                
                        // load fillArgs from storage if price is zero
                        if (orderInfo.fill.price == 0) {
                            orderInfo.fill = g_fillArgs;
                            g_fillArgs = FillArgs({
                                price: 0,
                                fee: 0,
                                isNegativeFee: false
                            });
                        }
                        Require.that(
                            orderInfo.fill.price != 0,
                            FILE,
                            "FillArgs loaded price is zero"
                        );
                
                        orderInfo.orderHash = getOrderHash(orderInfo.order);
                
                        return orderInfo;
                    }
                
                    function verifySignature(
                        OrderInfo memory orderInfo,
                        bytes memory data
                    )
                        private
                        view
                    {
                        OrderStatus orderStatus = g_status[orderInfo.orderHash];
                
                        // verify valid signature or is pre-approved
                        if (orderStatus == OrderStatus.Null) {
                            bytes memory signature = parseSignature(data);
                            address signer = TypedSignature.recover(orderInfo.orderHash, signature);
                            Require.that(
                                orderInfo.order.makerAccountOwner == signer,
                                FILE,
                                "Order invalid signature",
                                orderInfo.orderHash
                            );
                        } else {
                            Require.that(
                                orderStatus != OrderStatus.Canceled,
                                FILE,
                                "Order canceled",
                                orderInfo.orderHash
                            );
                            assert(orderStatus == OrderStatus.Approved);
                        }
                    }
                
                    /**
                     * Verifies that the order is still fillable for the particular accounts and markets specified.
                     */
                    function verifyOrderInfo(
                        OrderInfo memory orderInfo,
                        Account.Info memory makerAccount,
                        Account.Info memory takerAccount,
                        uint256 inputMarketId,
                        uint256 outputMarketId,
                        Types.Wei memory inputWei
                    )
                        private
                        view
                    {
                        // verify fill price
                        FillArgs memory fill = orderInfo.fill;
                        bool validPrice = isBuy(orderInfo.order)
                            ? fill.price <= orderInfo.order.limitPrice
                            : fill.price >= orderInfo.order.limitPrice;
                        Require.that(
                            validPrice,
                            FILE,
                            "Fill invalid price"
                        );
                
                        // verify fill fee
                        bool validFee = isNegativeLimitFee(orderInfo.order)
                            ? (fill.fee >= orderInfo.order.limitFee) && fill.isNegativeFee
                            : (fill.fee <= orderInfo.order.limitFee) || fill.isNegativeFee;
                        Require.that(
                            validFee,
                            FILE,
                            "Fill invalid fee"
                        );
                
                        // verify triggerPrice
                        if (orderInfo.order.triggerPrice > 0) {
                            uint256 currentPrice = getCurrentPrice(
                                orderInfo.order.baseMarket,
                                orderInfo.order.quoteMarket
                            );
                            Require.that(
                                isBuy(orderInfo.order)
                                    ? currentPrice >= orderInfo.order.triggerPrice
                                    : currentPrice <= orderInfo.order.triggerPrice,
                                FILE,
                                "Order triggerPrice not triggered",
                                currentPrice
                            );
                        }
                
                        // verify expriy
                        Require.that(
                            orderInfo.order.expiration == 0 || orderInfo.order.expiration >= block.timestamp,
                            FILE,
                            "Order expired",
                            orderInfo.orderHash
                        );
                
                        // verify maker
                        Require.that(
                            makerAccount.owner == orderInfo.order.makerAccountOwner &&
                            makerAccount.number == orderInfo.order.makerAccountNumber,
                            FILE,
                            "Order maker account mismatch",
                            orderInfo.orderHash
                        );
                
                        // verify taker
                        Require.that(
                            takerAccount.owner == g_taker,
                            FILE,
                            "Order taker mismatch",
                            orderInfo.orderHash
                        );
                
                        // verify markets
                        Require.that(
                            (
                                orderInfo.order.baseMarket == outputMarketId &&
                                orderInfo.order.quoteMarket == inputMarketId
                            ) || (
                                orderInfo.order.quoteMarket == outputMarketId &&
                                orderInfo.order.baseMarket == inputMarketId
                            ),
                            FILE,
                            "Market mismatch",
                            orderInfo.orderHash
                        );
                
                        // verify inputWei is non-zero
                        Require.that(
                            !inputWei.isZero(),
                            FILE,
                            "InputWei is zero",
                            orderInfo.orderHash
                        );
                
                        // verify inputWei is positive if-and-only-if:
                        // 1) inputMarket is the baseMarket and the order is a buy order
                        // 2) inputMarket is the quoteMarket and the order is a sell order
                        Require.that(
                            inputWei.sign ==
                                ((orderInfo.order.baseMarket == inputMarketId) == isBuy(orderInfo.order)),
                            FILE,
                            "InputWei sign mismatch",
                            orderInfo.orderHash
                        );
                    }
                
                    /**
                     * Verifies that the order is decreasing the size of the maker's position.
                     */
                    function verifyDecreaseOnly(
                        Types.Par memory oldInputPar,
                        Types.Par memory newInputPar,
                        Types.AssetAmount memory assetAmount,
                        Account.Info memory makerAccount,
                        uint256 outputMarketId
                    )
                        private
                        view
                    {
                        // verify that the balance of inputMarketId is not increased
                        Require.that(
                            newInputPar.isZero()
                            || (newInputPar.value <= oldInputPar.value && newInputPar.sign == oldInputPar.sign),
                            FILE,
                            "inputMarket not decreased"
                        );
                
                        // verify that the balance of outputMarketId is not increased
                        Types.Wei memory oldOutputWei = SOLO_MARGIN.getAccountWei(makerAccount, outputMarketId);
                        Require.that(
                            assetAmount.value == 0
                            || (assetAmount.value <= oldOutputWei.value && assetAmount.sign != oldOutputWei.sign),
                            FILE,
                            "outputMarket not decreased"
                        );
                    }
                
                    /**
                     * Returns the AssetAmount for the outputMarketId given the order and the inputs. Updates the
                     * filled amount of the order in storage.
                     */
                    function getOutputAssetAmount(
                        uint256 inputMarketId,
                        uint256 outputMarketId,
                        Types.Wei memory inputWei,
                        OrderInfo memory orderInfo
                    )
                        private
                        returns (Types.AssetAmount memory)
                    {
                        uint256 fee = orderInfo.fill.price.getPartial(orderInfo.fill.fee, PRICE_BASE);
                        uint256 adjustedPrice = (isBuy(orderInfo.order) == orderInfo.fill.isNegativeFee)
                            ? orderInfo.fill.price.sub(fee)
                            : orderInfo.fill.price.add(fee);
                
                        uint256 outputAmount;
                        uint256 fillAmount;
                        if (orderInfo.order.quoteMarket == inputMarketId) {
                            outputAmount = inputWei.value.getPartial(PRICE_BASE, adjustedPrice);
                            fillAmount = outputAmount;
                        } else {
                            assert(orderInfo.order.quoteMarket == outputMarketId);
                            outputAmount = inputWei.value.getPartial(adjustedPrice, PRICE_BASE);
                            fillAmount = inputWei.value;
                        }
                
                        updateFilledAmount(orderInfo, fillAmount);
                
                        return Types.AssetAmount({
                            sign: !inputWei.sign,
                            denomination: Types.AssetDenomination.Wei,
                            ref: Types.AssetReference.Delta,
                            value: outputAmount
                        });
                    }
                
                    /**
                     * Increases the stored filled amount of the order by fillAmount.
                     * Returns the new total filled amount.
                     */
                    function updateFilledAmount(
                        OrderInfo memory orderInfo,
                        uint256 fillAmount
                    )
                        private
                    {
                        uint256 oldFilledAmount = g_filledAmount[orderInfo.orderHash];
                        uint256 totalFilledAmount = oldFilledAmount.add(fillAmount);
                        Require.that(
                            totalFilledAmount <= orderInfo.order.amount,
                            FILE,
                            "Cannot overfill order",
                            orderInfo.orderHash,
                            oldFilledAmount,
                            fillAmount
                        );
                
                        g_filledAmount[orderInfo.orderHash] = totalFilledAmount;
                
                        emit LogCanonicalOrderFilled(
                            orderInfo.orderHash,
                            orderInfo.order.makerAccountOwner,
                            fillAmount,
                            orderInfo.order.triggerPrice,
                            orderInfo.order.flags,
                            orderInfo.fill
                        );
                    }
                
                    /**
                     * Returns the current price of baseMarket divided by the current price of quoteMarket. This
                     * value is multiplied by 10^18.
                     */
                    function getCurrentPrice(
                        uint256 baseMarket,
                        uint256 quoteMarket
                    )
                        private
                        view
                        returns (uint256)
                    {
                        Monetary.Price memory basePrice = SOLO_MARGIN.getMarketPrice(baseMarket);
                        Monetary.Price memory quotePrice = SOLO_MARGIN.getMarketPrice(quoteMarket);
                        return basePrice.value.mul(PRICE_BASE).div(quotePrice.value);
                    }
                
                    // ============ Private Parsing Functions ============
                
                    /**
                     * Returns the EIP712 hash of an order.
                     */
                    function getOrderHash(
                        Order memory order
                    )
                        private
                        view
                        returns (bytes32)
                    {
                        // compute the overall signed struct hash
                        /* solium-disable-next-line indentation */
                        bytes32 structHash = keccak256(abi.encode(
                            EIP712_ORDER_STRUCT_SCHEMA_HASH,
                            order
                        ));
                
                        // compute eip712 compliant hash
                        /* solium-disable-next-line indentation */
                        return keccak256(abi.encodePacked(
                            EIP191_HEADER,
                            EIP712_DOMAIN_HASH,
                            structHash
                        ));
                    }
                
                    /**
                     * Parses out a signature from call data.
                     */
                    function parseSignature(
                        bytes memory data
                    )
                        private
                        pure
                        returns (bytes memory)
                    {
                        Require.that(
                            data.length == NUM_ORDER_AND_FILL_BYTES + NUM_SIGNATURE_BYTES,
                            FILE,
                            "Cannot parse signature from data"
                        );
                
                        bytes memory signature = new bytes(NUM_SIGNATURE_BYTES);
                
                        uint256 sigOffset = NUM_ORDER_AND_FILL_BYTES;
                        /* solium-disable-next-line security/no-inline-assembly */
                        assembly {
                            let sigStart := add(data, sigOffset)
                            mstore(add(signature, 0x020), mload(add(sigStart, 0x20)))
                            mstore(add(signature, 0x040), mload(add(sigStart, 0x40)))
                            mstore(add(signature, 0x042), mload(add(sigStart, 0x42)))
                        }
                
                        return signature;
                    }
                
                    /**
                     * Returns true if the order is a buy order.
                     */
                    function isBuy(
                        Order memory order
                    )
                        private
                        pure
                        returns (bool)
                    {
                        return (order.flags & IS_BUY_FLAG) != bytes32(0);
                    }
                
                    /**
                     * Returns true if the order is a decrease-only order.
                     */
                    function isDecreaseOnly(
                        Order memory order
                    )
                        private
                        pure
                        returns (bool)
                    {
                        return (order.flags & IS_DECREASE_ONLY_FLAG) != bytes32(0);
                    }
                
                    /**
                     * Returns true if the order's limitFee is negative.
                     */
                    function isNegativeLimitFee(
                        Order memory order
                    )
                        private
                        pure
                        returns (bool)
                    {
                        return (order.flags & IS_NEGATIVE_FEE_FLAG) != bytes32(0);
                    }
                }

                File 3 of 7: OperationImpl
                /*
                
                    Copyright 2019 dYdX Trading Inc.
                
                    Licensed under the Apache License, Version 2.0 (the "License");
                    you may not use this file except in compliance with the License.
                    You may obtain a copy of the License at
                
                    http://www.apache.org/licenses/LICENSE-2.0
                
                    Unless required by applicable law or agreed to in writing, software
                    distributed under the License is distributed on an "AS IS" BASIS,
                    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                    See the License for the specific language governing permissions and
                    limitations under the License.
                
                */
                
                pragma solidity 0.5.7;
                pragma experimental ABIEncoderV2;
                
                // File: openzeppelin-solidity/contracts/math/SafeMath.sol
                
                /**
                 * @title SafeMath
                 * @dev Unsigned math operations with safety checks that revert on error
                 */
                library SafeMath {
                    /**
                    * @dev Multiplies two unsigned integers, reverts on overflow.
                    */
                    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
                        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
                        // benefit is lost if 'b' is also tested.
                        // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
                        if (a == 0) {
                            return 0;
                        }
                
                        uint256 c = a * b;
                        require(c / a == b);
                
                        return c;
                    }
                
                    /**
                    * @dev Integer division of two unsigned integers truncating the quotient, reverts on division by zero.
                    */
                    function div(uint256 a, uint256 b) internal pure returns (uint256) {
                        // Solidity only automatically asserts when dividing by 0
                        require(b > 0);
                        uint256 c = a / b;
                        // assert(a == b * c + a % b); // There is no case in which this doesn't hold
                
                        return c;
                    }
                
                    /**
                    * @dev Subtracts two unsigned integers, reverts on overflow (i.e. if subtrahend is greater than minuend).
                    */
                    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                        require(b <= a);
                        uint256 c = a - b;
                
                        return c;
                    }
                
                    /**
                    * @dev Adds two unsigned integers, reverts on overflow.
                    */
                    function add(uint256 a, uint256 b) internal pure returns (uint256) {
                        uint256 c = a + b;
                        require(c >= a);
                
                        return c;
                    }
                
                    /**
                    * @dev Divides two unsigned integers and returns the remainder (unsigned integer modulo),
                    * reverts when dividing by zero.
                    */
                    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
                        require(b != 0);
                        return a % b;
                    }
                }
                
                // File: contracts/protocol/lib/Require.sol
                
                /**
                 * @title Require
                 * @author dYdX
                 *
                 * Stringifies parameters to pretty-print revert messages. Costs more gas than regular require()
                 */
                library Require {
                
                    // ============ Constants ============
                
                    uint256 constant ASCII_ZERO = 48; // '0'
                    uint256 constant ASCII_RELATIVE_ZERO = 87; // 'a' - 10
                    uint256 constant ASCII_LOWER_EX = 120; // 'x'
                    bytes2 constant COLON = 0x3a20; // ': '
                    bytes2 constant COMMA = 0x2c20; // ', '
                    bytes2 constant LPAREN = 0x203c; // ' <'
                    byte constant RPAREN = 0x3e; // '>'
                    uint256 constant FOUR_BIT_MASK = 0xf;
                
                    // ============ Library Functions ============
                
                    function that(
                        bool must,
                        bytes32 file,
                        bytes32 reason
                    )
                        internal
                        pure
                    {
                        if (!must) {
                            revert(
                                string(
                                    abi.encodePacked(
                                        stringify(file),
                                        COLON,
                                        stringify(reason)
                                    )
                                )
                            );
                        }
                    }
                
                    function that(
                        bool must,
                        bytes32 file,
                        bytes32 reason,
                        uint256 payloadA
                    )
                        internal
                        pure
                    {
                        if (!must) {
                            revert(
                                string(
                                    abi.encodePacked(
                                        stringify(file),
                                        COLON,
                                        stringify(reason),
                                        LPAREN,
                                        stringify(payloadA),
                                        RPAREN
                                    )
                                )
                            );
                        }
                    }
                
                    function that(
                        bool must,
                        bytes32 file,
                        bytes32 reason,
                        uint256 payloadA,
                        uint256 payloadB
                    )
                        internal
                        pure
                    {
                        if (!must) {
                            revert(
                                string(
                                    abi.encodePacked(
                                        stringify(file),
                                        COLON,
                                        stringify(reason),
                                        LPAREN,
                                        stringify(payloadA),
                                        COMMA,
                                        stringify(payloadB),
                                        RPAREN
                                    )
                                )
                            );
                        }
                    }
                
                    function that(
                        bool must,
                        bytes32 file,
                        bytes32 reason,
                        address payloadA
                    )
                        internal
                        pure
                    {
                        if (!must) {
                            revert(
                                string(
                                    abi.encodePacked(
                                        stringify(file),
                                        COLON,
                                        stringify(reason),
                                        LPAREN,
                                        stringify(payloadA),
                                        RPAREN
                                    )
                                )
                            );
                        }
                    }
                
                    function that(
                        bool must,
                        bytes32 file,
                        bytes32 reason,
                        address payloadA,
                        uint256 payloadB
                    )
                        internal
                        pure
                    {
                        if (!must) {
                            revert(
                                string(
                                    abi.encodePacked(
                                        stringify(file),
                                        COLON,
                                        stringify(reason),
                                        LPAREN,
                                        stringify(payloadA),
                                        COMMA,
                                        stringify(payloadB),
                                        RPAREN
                                    )
                                )
                            );
                        }
                    }
                
                    function that(
                        bool must,
                        bytes32 file,
                        bytes32 reason,
                        address payloadA,
                        uint256 payloadB,
                        uint256 payloadC
                    )
                        internal
                        pure
                    {
                        if (!must) {
                            revert(
                                string(
                                    abi.encodePacked(
                                        stringify(file),
                                        COLON,
                                        stringify(reason),
                                        LPAREN,
                                        stringify(payloadA),
                                        COMMA,
                                        stringify(payloadB),
                                        COMMA,
                                        stringify(payloadC),
                                        RPAREN
                                    )
                                )
                            );
                        }
                    }
                
                    // ============ Private Functions ============
                
                    function stringify(
                        bytes32 input
                    )
                        private
                        pure
                        returns (bytes memory)
                    {
                        // put the input bytes into the result
                        bytes memory result = abi.encodePacked(input);
                
                        // determine the length of the input by finding the location of the last non-zero byte
                        for (uint256 i = 32; i > 0; ) {
                            // reverse-for-loops with unsigned integer
                            /* solium-disable-next-line security/no-modify-for-iter-var */
                            i--;
                
                            // find the last non-zero byte in order to determine the length
                            if (result[i] != 0) {
                                uint256 length = i + 1;
                
                                /* solium-disable-next-line security/no-inline-assembly */
                                assembly {
                                    mstore(result, length) // r.length = length;
                                }
                
                                return result;
                            }
                        }
                
                        // all bytes are zero
                        return new bytes(0);
                    }
                
                    function stringify(
                        uint256 input
                    )
                        private
                        pure
                        returns (bytes memory)
                    {
                        if (input == 0) {
                            return "0";
                        }
                
                        // get the final string length
                        uint256 j = input;
                        uint256 length;
                        while (j != 0) {
                            length++;
                            j /= 10;
                        }
                
                        // allocate the string
                        bytes memory bstr = new bytes(length);
                
                        // populate the string starting with the least-significant character
                        j = input;
                        for (uint256 i = length; i > 0; ) {
                            // reverse-for-loops with unsigned integer
                            /* solium-disable-next-line security/no-modify-for-iter-var */
                            i--;
                
                            // take last decimal digit
                            bstr[i] = byte(uint8(ASCII_ZERO + (j % 10)));
                
                            // remove the last decimal digit
                            j /= 10;
                        }
                
                        return bstr;
                    }
                
                    function stringify(
                        address input
                    )
                        private
                        pure
                        returns (bytes memory)
                    {
                        uint256 z = uint256(input);
                
                        // addresses are "0x" followed by 20 bytes of data which take up 2 characters each
                        bytes memory result = new bytes(42);
                
                        // populate the result with "0x"
                        result[0] = byte(uint8(ASCII_ZERO));
                        result[1] = byte(uint8(ASCII_LOWER_EX));
                
                        // for each byte (starting from the lowest byte), populate the result with two characters
                        for (uint256 i = 0; i < 20; i++) {
                            // each byte takes two characters
                            uint256 shift = i * 2;
                
                            // populate the least-significant character
                            result[41 - shift] = char(z & FOUR_BIT_MASK);
                            z = z >> 4;
                
                            // populate the most-significant character
                            result[40 - shift] = char(z & FOUR_BIT_MASK);
                            z = z >> 4;
                        }
                
                        return result;
                    }
                
                    function char(
                        uint256 input
                    )
                        private
                        pure
                        returns (byte)
                    {
                        // return ASCII digit (0-9)
                        if (input < 10) {
                            return byte(uint8(input + ASCII_ZERO));
                        }
                
                        // return ASCII letter (a-f)
                        return byte(uint8(input + ASCII_RELATIVE_ZERO));
                    }
                }
                
                // File: contracts/protocol/lib/Math.sol
                
                /**
                 * @title Math
                 * @author dYdX
                 *
                 * Library for non-standard Math functions
                 */
                library Math {
                    using SafeMath for uint256;
                
                    // ============ Constants ============
                
                    bytes32 constant FILE = "Math";
                
                    // ============ Library Functions ============
                
                    /*
                     * Return target * (numerator / denominator).
                     */
                    function getPartial(
                        uint256 target,
                        uint256 numerator,
                        uint256 denominator
                    )
                        internal
                        pure
                        returns (uint256)
                    {
                        return target.mul(numerator).div(denominator);
                    }
                
                    /*
                     * Return target * (numerator / denominator), but rounded up.
                     */
                    function getPartialRoundUp(
                        uint256 target,
                        uint256 numerator,
                        uint256 denominator
                    )
                        internal
                        pure
                        returns (uint256)
                    {
                        if (target == 0 || numerator == 0) {
                            // SafeMath will check for zero denominator
                            return SafeMath.div(0, denominator);
                        }
                        return target.mul(numerator).sub(1).div(denominator).add(1);
                    }
                
                    function to128(
                        uint256 number
                    )
                        internal
                        pure
                        returns (uint128)
                    {
                        uint128 result = uint128(number);
                        Require.that(
                            result == number,
                            FILE,
                            "Unsafe cast to uint128"
                        );
                        return result;
                    }
                
                    function to96(
                        uint256 number
                    )
                        internal
                        pure
                        returns (uint96)
                    {
                        uint96 result = uint96(number);
                        Require.that(
                            result == number,
                            FILE,
                            "Unsafe cast to uint96"
                        );
                        return result;
                    }
                
                    function to32(
                        uint256 number
                    )
                        internal
                        pure
                        returns (uint32)
                    {
                        uint32 result = uint32(number);
                        Require.that(
                            result == number,
                            FILE,
                            "Unsafe cast to uint32"
                        );
                        return result;
                    }
                
                    function min(
                        uint256 a,
                        uint256 b
                    )
                        internal
                        pure
                        returns (uint256)
                    {
                        return a < b ? a : b;
                    }
                
                    function max(
                        uint256 a,
                        uint256 b
                    )
                        internal
                        pure
                        returns (uint256)
                    {
                        return a > b ? a : b;
                    }
                }
                
                // File: contracts/protocol/lib/Types.sol
                
                /**
                 * @title Types
                 * @author dYdX
                 *
                 * Library for interacting with the basic structs used in Solo
                 */
                library Types {
                    using Math for uint256;
                
                    // ============ AssetAmount ============
                
                    enum AssetDenomination {
                        Wei, // the amount is denominated in wei
                        Par  // the amount is denominated in par
                    }
                
                    enum AssetReference {
                        Delta, // the amount is given as a delta from the current value
                        Target // the amount is given as an exact number to end up at
                    }
                
                    struct AssetAmount {
                        bool sign; // true if positive
                        AssetDenomination denomination;
                        AssetReference ref;
                        uint256 value;
                    }
                
                    // ============ Par (Principal Amount) ============
                
                    // Total borrow and supply values for a market
                    struct TotalPar {
                        uint128 borrow;
                        uint128 supply;
                    }
                
                    // Individual principal amount for an account
                    struct Par {
                        bool sign; // true if positive
                        uint128 value;
                    }
                
                    function zeroPar()
                        internal
                        pure
                        returns (Par memory)
                    {
                        return Par({
                            sign: false,
                            value: 0
                        });
                    }
                
                    function sub(
                        Par memory a,
                        Par memory b
                    )
                        internal
                        pure
                        returns (Par memory)
                    {
                        return add(a, negative(b));
                    }
                
                    function add(
                        Par memory a,
                        Par memory b
                    )
                        internal
                        pure
                        returns (Par memory)
                    {
                        Par memory result;
                        if (a.sign == b.sign) {
                            result.sign = a.sign;
                            result.value = SafeMath.add(a.value, b.value).to128();
                        } else {
                            if (a.value >= b.value) {
                                result.sign = a.sign;
                                result.value = SafeMath.sub(a.value, b.value).to128();
                            } else {
                                result.sign = b.sign;
                                result.value = SafeMath.sub(b.value, a.value).to128();
                            }
                        }
                        return result;
                    }
                
                    function equals(
                        Par memory a,
                        Par memory b
                    )
                        internal
                        pure
                        returns (bool)
                    {
                        if (a.value == b.value) {
                            if (a.value == 0) {
                                return true;
                            }
                            return a.sign == b.sign;
                        }
                        return false;
                    }
                
                    function negative(
                        Par memory a
                    )
                        internal
                        pure
                        returns (Par memory)
                    {
                        return Par({
                            sign: !a.sign,
                            value: a.value
                        });
                    }
                
                    function isNegative(
                        Par memory a
                    )
                        internal
                        pure
                        returns (bool)
                    {
                        return !a.sign && a.value > 0;
                    }
                
                    function isPositive(
                        Par memory a
                    )
                        internal
                        pure
                        returns (bool)
                    {
                        return a.sign && a.value > 0;
                    }
                
                    function isZero(
                        Par memory a
                    )
                        internal
                        pure
                        returns (bool)
                    {
                        return a.value == 0;
                    }
                
                    // ============ Wei (Token Amount) ============
                
                    // Individual token amount for an account
                    struct Wei {
                        bool sign; // true if positive
                        uint256 value;
                    }
                
                    function zeroWei()
                        internal
                        pure
                        returns (Wei memory)
                    {
                        return Wei({
                            sign: false,
                            value: 0
                        });
                    }
                
                    function sub(
                        Wei memory a,
                        Wei memory b
                    )
                        internal
                        pure
                        returns (Wei memory)
                    {
                        return add(a, negative(b));
                    }
                
                    function add(
                        Wei memory a,
                        Wei memory b
                    )
                        internal
                        pure
                        returns (Wei memory)
                    {
                        Wei memory result;
                        if (a.sign == b.sign) {
                            result.sign = a.sign;
                            result.value = SafeMath.add(a.value, b.value);
                        } else {
                            if (a.value >= b.value) {
                                result.sign = a.sign;
                                result.value = SafeMath.sub(a.value, b.value);
                            } else {
                                result.sign = b.sign;
                                result.value = SafeMath.sub(b.value, a.value);
                            }
                        }
                        return result;
                    }
                
                    function equals(
                        Wei memory a,
                        Wei memory b
                    )
                        internal
                        pure
                        returns (bool)
                    {
                        if (a.value == b.value) {
                            if (a.value == 0) {
                                return true;
                            }
                            return a.sign == b.sign;
                        }
                        return false;
                    }
                
                    function negative(
                        Wei memory a
                    )
                        internal
                        pure
                        returns (Wei memory)
                    {
                        return Wei({
                            sign: !a.sign,
                            value: a.value
                        });
                    }
                
                    function isNegative(
                        Wei memory a
                    )
                        internal
                        pure
                        returns (bool)
                    {
                        return !a.sign && a.value > 0;
                    }
                
                    function isPositive(
                        Wei memory a
                    )
                        internal
                        pure
                        returns (bool)
                    {
                        return a.sign && a.value > 0;
                    }
                
                    function isZero(
                        Wei memory a
                    )
                        internal
                        pure
                        returns (bool)
                    {
                        return a.value == 0;
                    }
                }
                
                // File: contracts/protocol/lib/Account.sol
                
                /**
                 * @title Account
                 * @author dYdX
                 *
                 * Library of structs and functions that represent an account
                 */
                library Account {
                    // ============ Enums ============
                
                    /*
                     * Most-recently-cached account status.
                     *
                     * Normal: Can only be liquidated if the account values are violating the global margin-ratio.
                     * Liquid: Can be liquidated no matter the account values.
                     *         Can be vaporized if there are no more positive account values.
                     * Vapor:  Has only negative (or zeroed) account values. Can be vaporized.
                     *
                     */
                    enum Status {
                        Normal,
                        Liquid,
                        Vapor
                    }
                
                    // ============ Structs ============
                
                    // Represents the unique key that specifies an account
                    struct Info {
                        address owner;  // The address that owns the account
                        uint256 number; // A nonce that allows a single address to control many accounts
                    }
                
                    // The complete storage for any account
                    struct Storage {
                        mapping (uint256 => Types.Par) balances; // Mapping from marketId to principal
                        Status status;
                    }
                
                    // ============ Library Functions ============
                
                    function equals(
                        Info memory a,
                        Info memory b
                    )
                        internal
                        pure
                        returns (bool)
                    {
                        return a.owner == b.owner && a.number == b.number;
                    }
                }
                
                // File: contracts/protocol/interfaces/IAutoTrader.sol
                
                /**
                 * @title IAutoTrader
                 * @author dYdX
                 *
                 * Interface that Auto-Traders for Solo must implement in order to approve trades.
                 */
                contract IAutoTrader {
                
                    // ============ Public Functions ============
                
                    /**
                     * Allows traders to make trades approved by this smart contract. The active trader's account is
                     * the takerAccount and the passive account (for which this contract approves trades
                     * on-behalf-of) is the makerAccount.
                     *
                     * @param  inputMarketId   The market for which the trader specified the original amount
                     * @param  outputMarketId  The market for which the trader wants the resulting amount specified
                     * @param  makerAccount    The account for which this contract is making trades
                     * @param  takerAccount    The account requesting the trade
                     * @param  oldInputPar     The old principal amount for the makerAccount for the inputMarketId
                     * @param  newInputPar     The new principal amount for the makerAccount for the inputMarketId
                     * @param  inputWei        The change in token amount for the makerAccount for the inputMarketId
                     * @param  data            Arbitrary data passed in by the trader
                     * @return                 The AssetAmount for the makerAccount for the outputMarketId
                     */
                    function getTradeCost(
                        uint256 inputMarketId,
                        uint256 outputMarketId,
                        Account.Info memory makerAccount,
                        Account.Info memory takerAccount,
                        Types.Par memory oldInputPar,
                        Types.Par memory newInputPar,
                        Types.Wei memory inputWei,
                        bytes memory data
                    )
                        public
                        returns (Types.AssetAmount memory);
                }
                
                // File: contracts/protocol/interfaces/ICallee.sol
                
                /**
                 * @title ICallee
                 * @author dYdX
                 *
                 * Interface that Callees for Solo must implement in order to ingest data.
                 */
                contract ICallee {
                
                    // ============ Public Functions ============
                
                    /**
                     * Allows users to send this contract arbitrary data.
                     *
                     * @param  sender       The msg.sender to Solo
                     * @param  accountInfo  The account from which the data is being sent
                     * @param  data         Arbitrary data given by the sender
                     */
                    function callFunction(
                        address sender,
                        Account.Info memory accountInfo,
                        bytes memory data
                    )
                        public;
                }
                
                // File: contracts/protocol/lib/Actions.sol
                
                /**
                 * @title Actions
                 * @author dYdX
                 *
                 * Library that defines and parses valid Actions
                 */
                library Actions {
                
                    // ============ Constants ============
                
                    bytes32 constant FILE = "Actions";
                
                    // ============ Enums ============
                
                    enum ActionType {
                        Deposit,   // supply tokens
                        Withdraw,  // borrow tokens
                        Transfer,  // transfer balance between accounts
                        Buy,       // buy an amount of some token (externally)
                        Sell,      // sell an amount of some token (externally)
                        Trade,     // trade tokens against another account
                        Liquidate, // liquidate an undercollateralized or expiring account
                        Vaporize,  // use excess tokens to zero-out a completely negative account
                        Call       // send arbitrary data to an address
                    }
                
                    enum AccountLayout {
                        OnePrimary,
                        TwoPrimary,
                        PrimaryAndSecondary
                    }
                
                    enum MarketLayout {
                        ZeroMarkets,
                        OneMarket,
                        TwoMarkets
                    }
                
                    // ============ Structs ============
                
                    /*
                     * Arguments that are passed to Solo in an ordered list as part of a single operation.
                     * Each ActionArgs has an actionType which specifies which action struct that this data will be
                     * parsed into before being processed.
                     */
                    struct ActionArgs {
                        ActionType actionType;
                        uint256 accountId;
                        Types.AssetAmount amount;
                        uint256 primaryMarketId;
                        uint256 secondaryMarketId;
                        address otherAddress;
                        uint256 otherAccountId;
                        bytes data;
                    }
                
                    // ============ Action Types ============
                
                    /*
                     * Moves tokens from an address to Solo. Can either repay a borrow or provide additional supply.
                     */
                    struct DepositArgs {
                        Types.AssetAmount amount;
                        Account.Info account;
                        uint256 market;
                        address from;
                    }
                
                    /*
                     * Moves tokens from Solo to another address. Can either borrow tokens or reduce the amount
                     * previously supplied.
                     */
                    struct WithdrawArgs {
                        Types.AssetAmount amount;
                        Account.Info account;
                        uint256 market;
                        address to;
                    }
                
                    /*
                     * Transfers balance between two accounts. The msg.sender must be an operator for both accounts.
                     * The amount field applies to accountOne.
                     * This action does not require any token movement since the trade is done internally to Solo.
                     */
                    struct TransferArgs {
                        Types.AssetAmount amount;
                        Account.Info accountOne;
                        Account.Info accountTwo;
                        uint256 market;
                    }
                
                    /*
                     * Acquires a certain amount of tokens by spending other tokens. Sends takerMarket tokens to the
                     * specified exchangeWrapper contract and expects makerMarket tokens in return. The amount field
                     * applies to the makerMarket.
                     */
                    struct BuyArgs {
                        Types.AssetAmount amount;
                        Account.Info account;
                        uint256 makerMarket;
                        uint256 takerMarket;
                        address exchangeWrapper;
                        bytes orderData;
                    }
                
                    /*
                     * Spends a certain amount of tokens to acquire other tokens. Sends takerMarket tokens to the
                     * specified exchangeWrapper and expects makerMarket tokens in return. The amount field applies
                     * to the takerMarket.
                     */
                    struct SellArgs {
                        Types.AssetAmount amount;
                        Account.Info account;
                        uint256 takerMarket;
                        uint256 makerMarket;
                        address exchangeWrapper;
                        bytes orderData;
                    }
                
                    /*
                     * Trades balances between two accounts using any external contract that implements the
                     * AutoTrader interface. The AutoTrader contract must be an operator for the makerAccount (for
                     * which it is trading on-behalf-of). The amount field applies to the makerAccount and the
                     * inputMarket. This proposed change to the makerAccount is passed to the AutoTrader which will
                     * quote a change for the makerAccount in the outputMarket (or will disallow the trade).
                     * This action does not require any token movement since the trade is done internally to Solo.
                     */
                    struct TradeArgs {
                        Types.AssetAmount amount;
                        Account.Info takerAccount;
                        Account.Info makerAccount;
                        uint256 inputMarket;
                        uint256 outputMarket;
                        address autoTrader;
                        bytes tradeData;
                    }
                
                    /*
                     * Each account must maintain a certain margin-ratio (specified globally). If the account falls
                     * below this margin-ratio, it can be liquidated by any other account. This allows anyone else
                     * (arbitrageurs) to repay any borrowed asset (owedMarket) of the liquidating account in
                     * exchange for any collateral asset (heldMarket) of the liquidAccount. The ratio is determined
                     * by the price ratio (given by the oracles) plus a spread (specified globally). Liquidating an
                     * account also sets a flag on the account that the account is being liquidated. This allows
                     * anyone to continue liquidating the account until there are no more borrows being taken by the
                     * liquidating account. Liquidators do not have to liquidate the entire account all at once but
                     * can liquidate as much as they choose. The liquidating flag allows liquidators to continue
                     * liquidating the account even if it becomes collateralized through partial liquidation or
                     * price movement.
                     */
                    struct LiquidateArgs {
                        Types.AssetAmount amount;
                        Account.Info solidAccount;
                        Account.Info liquidAccount;
                        uint256 owedMarket;
                        uint256 heldMarket;
                    }
                
                    /*
                     * Similar to liquidate, but vaporAccounts are accounts that have only negative balances
                     * remaining. The arbitrageur pays back the negative asset (owedMarket) of the vaporAccount in
                     * exchange for a collateral asset (heldMarket) at a favorable spread. However, since the
                     * liquidAccount has no collateral assets, the collateral must come from Solo's excess tokens.
                     */
                    struct VaporizeArgs {
                        Types.AssetAmount amount;
                        Account.Info solidAccount;
                        Account.Info vaporAccount;
                        uint256 owedMarket;
                        uint256 heldMarket;
                    }
                
                    /*
                     * Passes arbitrary bytes of data to an external contract that implements the Callee interface.
                     * Does not change any asset amounts. This function may be useful for setting certain variables
                     * on layer-two contracts for certain accounts without having to make a separate Ethereum
                     * transaction for doing so. Also, the second-layer contracts can ensure that the call is coming
                     * from an operator of the particular account.
                     */
                    struct CallArgs {
                        Account.Info account;
                        address callee;
                        bytes data;
                    }
                
                    // ============ Helper Functions ============
                
                    function getMarketLayout(
                        ActionType actionType
                    )
                        internal
                        pure
                        returns (MarketLayout)
                    {
                        if (
                            actionType == Actions.ActionType.Deposit
                            || actionType == Actions.ActionType.Withdraw
                            || actionType == Actions.ActionType.Transfer
                        ) {
                            return MarketLayout.OneMarket;
                        }
                        else if (actionType == Actions.ActionType.Call) {
                            return MarketLayout.ZeroMarkets;
                        }
                        return MarketLayout.TwoMarkets;
                    }
                
                    function getAccountLayout(
                        ActionType actionType
                    )
                        internal
                        pure
                        returns (AccountLayout)
                    {
                        if (
                            actionType == Actions.ActionType.Transfer
                            || actionType == Actions.ActionType.Trade
                        ) {
                            return AccountLayout.TwoPrimary;
                        } else if (
                            actionType == Actions.ActionType.Liquidate
                            || actionType == Actions.ActionType.Vaporize
                        ) {
                            return AccountLayout.PrimaryAndSecondary;
                        }
                        return AccountLayout.OnePrimary;
                    }
                
                    // ============ Parsing Functions ============
                
                    function parseDepositArgs(
                        Account.Info[] memory accounts,
                        ActionArgs memory args
                    )
                        internal
                        pure
                        returns (DepositArgs memory)
                    {
                        assert(args.actionType == ActionType.Deposit);
                        return DepositArgs({
                            amount: args.amount,
                            account: accounts[args.accountId],
                            market: args.primaryMarketId,
                            from: args.otherAddress
                        });
                    }
                
                    function parseWithdrawArgs(
                        Account.Info[] memory accounts,
                        ActionArgs memory args
                    )
                        internal
                        pure
                        returns (WithdrawArgs memory)
                    {
                        assert(args.actionType == ActionType.Withdraw);
                        return WithdrawArgs({
                            amount: args.amount,
                            account: accounts[args.accountId],
                            market: args.primaryMarketId,
                            to: args.otherAddress
                        });
                    }
                
                    function parseTransferArgs(
                        Account.Info[] memory accounts,
                        ActionArgs memory args
                    )
                        internal
                        pure
                        returns (TransferArgs memory)
                    {
                        assert(args.actionType == ActionType.Transfer);
                        return TransferArgs({
                            amount: args.amount,
                            accountOne: accounts[args.accountId],
                            accountTwo: accounts[args.otherAccountId],
                            market: args.primaryMarketId
                        });
                    }
                
                    function parseBuyArgs(
                        Account.Info[] memory accounts,
                        ActionArgs memory args
                    )
                        internal
                        pure
                        returns (BuyArgs memory)
                    {
                        assert(args.actionType == ActionType.Buy);
                        return BuyArgs({
                            amount: args.amount,
                            account: accounts[args.accountId],
                            makerMarket: args.primaryMarketId,
                            takerMarket: args.secondaryMarketId,
                            exchangeWrapper: args.otherAddress,
                            orderData: args.data
                        });
                    }
                
                    function parseSellArgs(
                        Account.Info[] memory accounts,
                        ActionArgs memory args
                    )
                        internal
                        pure
                        returns (SellArgs memory)
                    {
                        assert(args.actionType == ActionType.Sell);
                        return SellArgs({
                            amount: args.amount,
                            account: accounts[args.accountId],
                            takerMarket: args.primaryMarketId,
                            makerMarket: args.secondaryMarketId,
                            exchangeWrapper: args.otherAddress,
                            orderData: args.data
                        });
                    }
                
                    function parseTradeArgs(
                        Account.Info[] memory accounts,
                        ActionArgs memory args
                    )
                        internal
                        pure
                        returns (TradeArgs memory)
                    {
                        assert(args.actionType == ActionType.Trade);
                        return TradeArgs({
                            amount: args.amount,
                            takerAccount: accounts[args.accountId],
                            makerAccount: accounts[args.otherAccountId],
                            inputMarket: args.primaryMarketId,
                            outputMarket: args.secondaryMarketId,
                            autoTrader: args.otherAddress,
                            tradeData: args.data
                        });
                    }
                
                    function parseLiquidateArgs(
                        Account.Info[] memory accounts,
                        ActionArgs memory args
                    )
                        internal
                        pure
                        returns (LiquidateArgs memory)
                    {
                        assert(args.actionType == ActionType.Liquidate);
                        return LiquidateArgs({
                            amount: args.amount,
                            solidAccount: accounts[args.accountId],
                            liquidAccount: accounts[args.otherAccountId],
                            owedMarket: args.primaryMarketId,
                            heldMarket: args.secondaryMarketId
                        });
                    }
                
                    function parseVaporizeArgs(
                        Account.Info[] memory accounts,
                        ActionArgs memory args
                    )
                        internal
                        pure
                        returns (VaporizeArgs memory)
                    {
                        assert(args.actionType == ActionType.Vaporize);
                        return VaporizeArgs({
                            amount: args.amount,
                            solidAccount: accounts[args.accountId],
                            vaporAccount: accounts[args.otherAccountId],
                            owedMarket: args.primaryMarketId,
                            heldMarket: args.secondaryMarketId
                        });
                    }
                
                    function parseCallArgs(
                        Account.Info[] memory accounts,
                        ActionArgs memory args
                    )
                        internal
                        pure
                        returns (CallArgs memory)
                    {
                        assert(args.actionType == ActionType.Call);
                        return CallArgs({
                            account: accounts[args.accountId],
                            callee: args.otherAddress,
                            data: args.data
                        });
                    }
                }
                
                // File: contracts/protocol/lib/Monetary.sol
                
                /**
                 * @title Monetary
                 * @author dYdX
                 *
                 * Library for types involving money
                 */
                library Monetary {
                
                    /*
                     * The price of a base-unit of an asset.
                     */
                    struct Price {
                        uint256 value;
                    }
                
                    /*
                     * Total value of an some amount of an asset. Equal to (price * amount).
                     */
                    struct Value {
                        uint256 value;
                    }
                }
                
                // File: contracts/protocol/lib/Cache.sol
                
                /**
                 * @title Cache
                 * @author dYdX
                 *
                 * Library for caching information about markets
                 */
                library Cache {
                    using Cache for MarketCache;
                    using Storage for Storage.State;
                
                    // ============ Structs ============
                
                    struct MarketInfo {
                        bool isClosing;
                        uint128 borrowPar;
                        Monetary.Price price;
                    }
                
                    struct MarketCache {
                        MarketInfo[] markets;
                    }
                
                    // ============ Setter Functions ============
                
                    /**
                     * Initialize an empty cache for some given number of total markets.
                     */
                    function create(
                        uint256 numMarkets
                    )
                        internal
                        pure
                        returns (MarketCache memory)
                    {
                        return MarketCache({
                            markets: new MarketInfo[](numMarkets)
                        });
                    }
                
                    /**
                     * Add market information (price and total borrowed par if the market is closing) to the cache.
                     * Return true if the market information did not previously exist in the cache.
                     */
                    function addMarket(
                        MarketCache memory cache,
                        Storage.State storage state,
                        uint256 marketId
                    )
                        internal
                        view
                        returns (bool)
                    {
                        if (cache.hasMarket(marketId)) {
                            return false;
                        }
                        cache.markets[marketId].price = state.fetchPrice(marketId);
                        if (state.markets[marketId].isClosing) {
                            cache.markets[marketId].isClosing = true;
                            cache.markets[marketId].borrowPar = state.getTotalPar(marketId).borrow;
                        }
                        return true;
                    }
                
                    // ============ Getter Functions ============
                
                    function getNumMarkets(
                        MarketCache memory cache
                    )
                        internal
                        pure
                        returns (uint256)
                    {
                        return cache.markets.length;
                    }
                
                    function hasMarket(
                        MarketCache memory cache,
                        uint256 marketId
                    )
                        internal
                        pure
                        returns (bool)
                    {
                        return cache.markets[marketId].price.value != 0;
                    }
                
                    function getIsClosing(
                        MarketCache memory cache,
                        uint256 marketId
                    )
                        internal
                        pure
                        returns (bool)
                    {
                        return cache.markets[marketId].isClosing;
                    }
                
                    function getPrice(
                        MarketCache memory cache,
                        uint256 marketId
                    )
                        internal
                        pure
                        returns (Monetary.Price memory)
                    {
                        return cache.markets[marketId].price;
                    }
                
                    function getBorrowPar(
                        MarketCache memory cache,
                        uint256 marketId
                    )
                        internal
                        pure
                        returns (uint128)
                    {
                        return cache.markets[marketId].borrowPar;
                    }
                }
                
                // File: contracts/protocol/lib/Decimal.sol
                
                /**
                 * @title Decimal
                 * @author dYdX
                 *
                 * Library that defines a fixed-point number with 18 decimal places.
                 */
                library Decimal {
                    using SafeMath for uint256;
                
                    // ============ Constants ============
                
                    uint256 constant BASE = 10**18;
                
                    // ============ Structs ============
                
                    struct D256 {
                        uint256 value;
                    }
                
                    // ============ Functions ============
                
                    function one()
                        internal
                        pure
                        returns (D256 memory)
                    {
                        return D256({ value: BASE });
                    }
                
                    function onePlus(
                        D256 memory d
                    )
                        internal
                        pure
                        returns (D256 memory)
                    {
                        return D256({ value: d.value.add(BASE) });
                    }
                
                    function mul(
                        uint256 target,
                        D256 memory d
                    )
                        internal
                        pure
                        returns (uint256)
                    {
                        return Math.getPartial(target, d.value, BASE);
                    }
                
                    function div(
                        uint256 target,
                        D256 memory d
                    )
                        internal
                        pure
                        returns (uint256)
                    {
                        return Math.getPartial(target, BASE, d.value);
                    }
                }
                
                // File: contracts/protocol/lib/Time.sol
                
                /**
                 * @title Time
                 * @author dYdX
                 *
                 * Library for dealing with time, assuming timestamps fit within 32 bits (valid until year 2106)
                 */
                library Time {
                
                    // ============ Library Functions ============
                
                    function currentTime()
                        internal
                        view
                        returns (uint32)
                    {
                        return Math.to32(block.timestamp);
                    }
                }
                
                // File: contracts/protocol/lib/Interest.sol
                
                /**
                 * @title Interest
                 * @author dYdX
                 *
                 * Library for managing the interest rate and interest indexes of Solo
                 */
                library Interest {
                    using Math for uint256;
                    using SafeMath for uint256;
                
                    // ============ Constants ============
                
                    bytes32 constant FILE = "Interest";
                    uint64 constant BASE = 10**18;
                
                    // ============ Structs ============
                
                    struct Rate {
                        uint256 value;
                    }
                
                    struct Index {
                        uint96 borrow;
                        uint96 supply;
                        uint32 lastUpdate;
                    }
                
                    // ============ Library Functions ============
                
                    /**
                     * Get a new market Index based on the old index and market interest rate.
                     * Calculate interest for borrowers by using the formula rate * time. Approximates
                     * continuously-compounded interest when called frequently, but is much more
                     * gas-efficient to calculate. For suppliers, the interest rate is adjusted by the earningsRate,
                     * then prorated the across all suppliers.
                     *
                     * @param  index         The old index for a market
                     * @param  rate          The current interest rate of the market
                     * @param  totalPar      The total supply and borrow par values of the market
                     * @param  earningsRate  The portion of the interest that is forwarded to the suppliers
                     * @return               The updated index for a market
                     */
                    function calculateNewIndex(
                        Index memory index,
                        Rate memory rate,
                        Types.TotalPar memory totalPar,
                        Decimal.D256 memory earningsRate
                    )
                        internal
                        view
                        returns (Index memory)
                    {
                        (
                            Types.Wei memory supplyWei,
                            Types.Wei memory borrowWei
                        ) = totalParToWei(totalPar, index);
                
                        // get interest increase for borrowers
                        uint32 currentTime = Time.currentTime();
                        uint256 borrowInterest = rate.value.mul(uint256(currentTime).sub(index.lastUpdate));
                
                        // get interest increase for suppliers
                        uint256 supplyInterest;
                        if (Types.isZero(supplyWei)) {
                            supplyInterest = 0;
                        } else {
                            supplyInterest = Decimal.mul(borrowInterest, earningsRate);
                            if (borrowWei.value < supplyWei.value) {
                                supplyInterest = Math.getPartial(supplyInterest, borrowWei.value, supplyWei.value);
                            }
                        }
                        assert(supplyInterest <= borrowInterest);
                
                        return Index({
                            borrow: Math.getPartial(index.borrow, borrowInterest, BASE).add(index.borrow).to96(),
                            supply: Math.getPartial(index.supply, supplyInterest, BASE).add(index.supply).to96(),
                            lastUpdate: currentTime
                        });
                    }
                
                    function newIndex()
                        internal
                        view
                        returns (Index memory)
                    {
                        return Index({
                            borrow: BASE,
                            supply: BASE,
                            lastUpdate: Time.currentTime()
                        });
                    }
                
                    /*
                     * Convert a principal amount to a token amount given an index.
                     */
                    function parToWei(
                        Types.Par memory input,
                        Index memory index
                    )
                        internal
                        pure
                        returns (Types.Wei memory)
                    {
                        uint256 inputValue = uint256(input.value);
                        if (input.sign) {
                            return Types.Wei({
                                sign: true,
                                value: inputValue.getPartial(index.supply, BASE)
                            });
                        } else {
                            return Types.Wei({
                                sign: false,
                                value: inputValue.getPartialRoundUp(index.borrow, BASE)
                            });
                        }
                    }
                
                    /*
                     * Convert a token amount to a principal amount given an index.
                     */
                    function weiToPar(
                        Types.Wei memory input,
                        Index memory index
                    )
                        internal
                        pure
                        returns (Types.Par memory)
                    {
                        if (input.sign) {
                            return Types.Par({
                                sign: true,
                                value: input.value.getPartial(BASE, index.supply).to128()
                            });
                        } else {
                            return Types.Par({
                                sign: false,
                                value: input.value.getPartialRoundUp(BASE, index.borrow).to128()
                            });
                        }
                    }
                
                    /*
                     * Convert the total supply and borrow principal amounts of a market to total supply and borrow
                     * token amounts.
                     */
                    function totalParToWei(
                        Types.TotalPar memory totalPar,
                        Index memory index
                    )
                        internal
                        pure
                        returns (Types.Wei memory, Types.Wei memory)
                    {
                        Types.Par memory supplyPar = Types.Par({
                            sign: true,
                            value: totalPar.supply
                        });
                        Types.Par memory borrowPar = Types.Par({
                            sign: false,
                            value: totalPar.borrow
                        });
                        Types.Wei memory supplyWei = parToWei(supplyPar, index);
                        Types.Wei memory borrowWei = parToWei(borrowPar, index);
                        return (supplyWei, borrowWei);
                    }
                }
                
                // File: contracts/protocol/interfaces/IErc20.sol
                
                /**
                 * @title IErc20
                 * @author dYdX
                 *
                 * Interface for using ERC20 Tokens. We have to use a special interface to call ERC20 functions so
                 * that we don't automatically revert when calling non-compliant tokens that have no return value for
                 * transfer(), transferFrom(), or approve().
                 */
                interface IErc20 {
                    event Transfer(
                        address indexed from,
                        address indexed to,
                        uint256 value
                    );
                
                    event Approval(
                        address indexed owner,
                        address indexed spender,
                        uint256 value
                    );
                
                    function totalSupply(
                    )
                        external
                        view
                        returns (uint256);
                
                    function balanceOf(
                        address who
                    )
                        external
                        view
                        returns (uint256);
                
                    function allowance(
                        address owner,
                        address spender
                    )
                        external
                        view
                        returns (uint256);
                
                    function transfer(
                        address to,
                        uint256 value
                    )
                        external;
                
                    function transferFrom(
                        address from,
                        address to,
                        uint256 value
                    )
                        external;
                
                    function approve(
                        address spender,
                        uint256 value
                    )
                        external;
                
                    function name()
                        external
                        view
                        returns (string memory);
                
                    function symbol()
                        external
                        view
                        returns (string memory);
                
                    function decimals()
                        external
                        view
                        returns (uint8);
                }
                
                // File: contracts/protocol/lib/Token.sol
                
                /**
                 * @title Token
                 * @author dYdX
                 *
                 * This library contains basic functions for interacting with ERC20 tokens. Modified to work with
                 * tokens that don't adhere strictly to the ERC20 standard (for example tokens that don't return a
                 * boolean value on success).
                 */
                library Token {
                
                    // ============ Constants ============
                
                    bytes32 constant FILE = "Token";
                
                    // ============ Library Functions ============
                
                    function balanceOf(
                        address token,
                        address owner
                    )
                        internal
                        view
                        returns (uint256)
                    {
                        return IErc20(token).balanceOf(owner);
                    }
                
                    function allowance(
                        address token,
                        address owner,
                        address spender
                    )
                        internal
                        view
                        returns (uint256)
                    {
                        return IErc20(token).allowance(owner, spender);
                    }
                
                    function approve(
                        address token,
                        address spender,
                        uint256 amount
                    )
                        internal
                    {
                        IErc20(token).approve(spender, amount);
                
                        Require.that(
                            checkSuccess(),
                            FILE,
                            "Approve failed"
                        );
                    }
                
                    function approveMax(
                        address token,
                        address spender
                    )
                        internal
                    {
                        approve(
                            token,
                            spender,
                            uint256(-1)
                        );
                    }
                
                    function transfer(
                        address token,
                        address to,
                        uint256 amount
                    )
                        internal
                    {
                        if (amount == 0 || to == address(this)) {
                            return;
                        }
                
                        IErc20(token).transfer(to, amount);
                
                        Require.that(
                            checkSuccess(),
                            FILE,
                            "Transfer failed"
                        );
                    }
                
                    function transferFrom(
                        address token,
                        address from,
                        address to,
                        uint256 amount
                    )
                        internal
                    {
                        if (amount == 0 || to == from) {
                            return;
                        }
                
                        IErc20(token).transferFrom(from, to, amount);
                
                        Require.that(
                            checkSuccess(),
                            FILE,
                            "TransferFrom failed"
                        );
                    }
                
                    // ============ Private Functions ============
                
                    /**
                     * Check the return value of the previous function up to 32 bytes. Return true if the previous
                     * function returned 0 bytes or 32 bytes that are not all-zero.
                     */
                    function checkSuccess(
                    )
                        private
                        pure
                        returns (bool)
                    {
                        uint256 returnValue = 0;
                
                        /* solium-disable-next-line security/no-inline-assembly */
                        assembly {
                            // check number of bytes returned from last function call
                            switch returndatasize
                
                            // no bytes returned: assume success
                            case 0x0 {
                                returnValue := 1
                            }
                
                            // 32 bytes returned: check if non-zero
                            case 0x20 {
                                // copy 32 bytes into scratch space
                                returndatacopy(0x0, 0x0, 0x20)
                
                                // load those bytes into returnValue
                                returnValue := mload(0x0)
                            }
                
                            // not sure what was returned: don't mark as success
                            default { }
                        }
                
                        return returnValue != 0;
                    }
                }
                
                // File: contracts/protocol/interfaces/IInterestSetter.sol
                
                /**
                 * @title IInterestSetter
                 * @author dYdX
                 *
                 * Interface that Interest Setters for Solo must implement in order to report interest rates.
                 */
                interface IInterestSetter {
                
                    // ============ Public Functions ============
                
                    /**
                     * Get the interest rate of a token given some borrowed and supplied amounts
                     *
                     * @param  token        The address of the ERC20 token for the market
                     * @param  borrowWei    The total borrowed token amount for the market
                     * @param  supplyWei    The total supplied token amount for the market
                     * @return              The interest rate per second
                     */
                    function getInterestRate(
                        address token,
                        uint256 borrowWei,
                        uint256 supplyWei
                    )
                        external
                        view
                        returns (Interest.Rate memory);
                }
                
                // File: contracts/protocol/interfaces/IPriceOracle.sol
                
                /**
                 * @title IPriceOracle
                 * @author dYdX
                 *
                 * Interface that Price Oracles for Solo must implement in order to report prices.
                 */
                contract IPriceOracle {
                
                    // ============ Constants ============
                
                    uint256 public constant ONE_DOLLAR = 10 ** 36;
                
                    // ============ Public Functions ============
                
                    /**
                     * Get the price of a token
                     *
                     * @param  token  The ERC20 token address of the market
                     * @return        The USD price of a base unit of the token, then multiplied by 10^36.
                     *                So a USD-stable coin with 18 decimal places would return 10^18.
                     *                This is the price of the base unit rather than the price of a "human-readable"
                     *                token amount. Every ERC20 may have a different number of decimals.
                     */
                    function getPrice(
                        address token
                    )
                        public
                        view
                        returns (Monetary.Price memory);
                }
                
                // File: contracts/protocol/lib/Storage.sol
                
                /**
                 * @title Storage
                 * @author dYdX
                 *
                 * Functions for reading, writing, and verifying state in Solo
                 */
                library Storage {
                    using Cache for Cache.MarketCache;
                    using Storage for Storage.State;
                    using Math for uint256;
                    using Types for Types.Par;
                    using Types for Types.Wei;
                    using SafeMath for uint256;
                
                    // ============ Constants ============
                
                    bytes32 constant FILE = "Storage";
                
                    // ============ Structs ============
                
                    // All information necessary for tracking a market
                    struct Market {
                        // Contract address of the associated ERC20 token
                        address token;
                
                        // Total aggregated supply and borrow amount of the entire market
                        Types.TotalPar totalPar;
                
                        // Interest index of the market
                        Interest.Index index;
                
                        // Contract address of the price oracle for this market
                        IPriceOracle priceOracle;
                
                        // Contract address of the interest setter for this market
                        IInterestSetter interestSetter;
                
                        // Multiplier on the marginRatio for this market
                        Decimal.D256 marginPremium;
                
                        // Multiplier on the liquidationSpread for this market
                        Decimal.D256 spreadPremium;
                
                        // Whether additional borrows are allowed for this market
                        bool isClosing;
                    }
                
                    // The global risk parameters that govern the health and security of the system
                    struct RiskParams {
                        // Required ratio of over-collateralization
                        Decimal.D256 marginRatio;
                
                        // Percentage penalty incurred by liquidated accounts
                        Decimal.D256 liquidationSpread;
                
                        // Percentage of the borrower's interest fee that gets passed to the suppliers
                        Decimal.D256 earningsRate;
                
                        // The minimum absolute borrow value of an account
                        // There must be sufficient incentivize to liquidate undercollateralized accounts
                        Monetary.Value minBorrowedValue;
                    }
                
                    // The maximum RiskParam values that can be set
                    struct RiskLimits {
                        uint64 marginRatioMax;
                        uint64 liquidationSpreadMax;
                        uint64 earningsRateMax;
                        uint64 marginPremiumMax;
                        uint64 spreadPremiumMax;
                        uint128 minBorrowedValueMax;
                    }
                
                    // The entire storage state of Solo
                    struct State {
                        // number of markets
                        uint256 numMarkets;
                
                        // marketId => Market
                        mapping (uint256 => Market) markets;
                
                        // owner => account number => Account
                        mapping (address => mapping (uint256 => Account.Storage)) accounts;
                
                        // Addresses that can control other users accounts
                        mapping (address => mapping (address => bool)) operators;
                
                        // Addresses that can control all users accounts
                        mapping (address => bool) globalOperators;
                
                        // mutable risk parameters of the system
                        RiskParams riskParams;
                
                        // immutable risk limits of the system
                        RiskLimits riskLimits;
                    }
                
                    // ============ Functions ============
                
                    function getToken(
                        Storage.State storage state,
                        uint256 marketId
                    )
                        internal
                        view
                        returns (address)
                    {
                        return state.markets[marketId].token;
                    }
                
                    function getTotalPar(
                        Storage.State storage state,
                        uint256 marketId
                    )
                        internal
                        view
                        returns (Types.TotalPar memory)
                    {
                        return state.markets[marketId].totalPar;
                    }
                
                    function getIndex(
                        Storage.State storage state,
                        uint256 marketId
                    )
                        internal
                        view
                        returns (Interest.Index memory)
                    {
                        return state.markets[marketId].index;
                    }
                
                    function getNumExcessTokens(
                        Storage.State storage state,
                        uint256 marketId
                    )
                        internal
                        view
                        returns (Types.Wei memory)
                    {
                        Interest.Index memory index = state.getIndex(marketId);
                        Types.TotalPar memory totalPar = state.getTotalPar(marketId);
                
                        address token = state.getToken(marketId);
                
                        Types.Wei memory balanceWei = Types.Wei({
                            sign: true,
                            value: Token.balanceOf(token, address(this))
                        });
                
                        (
                            Types.Wei memory supplyWei,
                            Types.Wei memory borrowWei
                        ) = Interest.totalParToWei(totalPar, index);
                
                        // borrowWei is negative, so subtracting it makes the value more positive
                        return balanceWei.sub(borrowWei).sub(supplyWei);
                    }
                
                    function getStatus(
                        Storage.State storage state,
                        Account.Info memory account
                    )
                        internal
                        view
                        returns (Account.Status)
                    {
                        return state.accounts[account.owner][account.number].status;
                    }
                
                    function getPar(
                        Storage.State storage state,
                        Account.Info memory account,
                        uint256 marketId
                    )
                        internal
                        view
                        returns (Types.Par memory)
                    {
                        return state.accounts[account.owner][account.number].balances[marketId];
                    }
                
                    function getWei(
                        Storage.State storage state,
                        Account.Info memory account,
                        uint256 marketId
                    )
                        internal
                        view
                        returns (Types.Wei memory)
                    {
                        Types.Par memory par = state.getPar(account, marketId);
                
                        if (par.isZero()) {
                            return Types.zeroWei();
                        }
                
                        Interest.Index memory index = state.getIndex(marketId);
                        return Interest.parToWei(par, index);
                    }
                
                    function getLiquidationSpreadForPair(
                        Storage.State storage state,
                        uint256 heldMarketId,
                        uint256 owedMarketId
                    )
                        internal
                        view
                        returns (Decimal.D256 memory)
                    {
                        uint256 result = state.riskParams.liquidationSpread.value;
                        result = Decimal.mul(result, Decimal.onePlus(state.markets[heldMarketId].spreadPremium));
                        result = Decimal.mul(result, Decimal.onePlus(state.markets[owedMarketId].spreadPremium));
                        return Decimal.D256({
                            value: result
                        });
                    }
                
                    function fetchNewIndex(
                        Storage.State storage state,
                        uint256 marketId,
                        Interest.Index memory index
                    )
                        internal
                        view
                        returns (Interest.Index memory)
                    {
                        Interest.Rate memory rate = state.fetchInterestRate(marketId, index);
                
                        return Interest.calculateNewIndex(
                            index,
                            rate,
                            state.getTotalPar(marketId),
                            state.riskParams.earningsRate
                        );
                    }
                
                    function fetchInterestRate(
                        Storage.State storage state,
                        uint256 marketId,
                        Interest.Index memory index
                    )
                        internal
                        view
                        returns (Interest.Rate memory)
                    {
                        Types.TotalPar memory totalPar = state.getTotalPar(marketId);
                        (
                            Types.Wei memory supplyWei,
                            Types.Wei memory borrowWei
                        ) = Interest.totalParToWei(totalPar, index);
                
                        Interest.Rate memory rate = state.markets[marketId].interestSetter.getInterestRate(
                            state.getToken(marketId),
                            borrowWei.value,
                            supplyWei.value
                        );
                
                        return rate;
                    }
                
                    function fetchPrice(
                        Storage.State storage state,
                        uint256 marketId
                    )
                        internal
                        view
                        returns (Monetary.Price memory)
                    {
                        IPriceOracle oracle = IPriceOracle(state.markets[marketId].priceOracle);
                        Monetary.Price memory price = oracle.getPrice(state.getToken(marketId));
                        Require.that(
                            price.value != 0,
                            FILE,
                            "Price cannot be zero",
                            marketId
                        );
                        return price;
                    }
                
                    function getAccountValues(
                        Storage.State storage state,
                        Account.Info memory account,
                        Cache.MarketCache memory cache,
                        bool adjustForLiquidity
                    )
                        internal
                        view
                        returns (Monetary.Value memory, Monetary.Value memory)
                    {
                        Monetary.Value memory supplyValue;
                        Monetary.Value memory borrowValue;
                
                        uint256 numMarkets = cache.getNumMarkets();
                        for (uint256 m = 0; m < numMarkets; m++) {
                            if (!cache.hasMarket(m)) {
                                continue;
                            }
                
                            Types.Wei memory userWei = state.getWei(account, m);
                
                            if (userWei.isZero()) {
                                continue;
                            }
                
                            uint256 assetValue = userWei.value.mul(cache.getPrice(m).value);
                            Decimal.D256 memory adjust = Decimal.one();
                            if (adjustForLiquidity) {
                                adjust = Decimal.onePlus(state.markets[m].marginPremium);
                            }
                
                            if (userWei.sign) {
                                supplyValue.value = supplyValue.value.add(Decimal.div(assetValue, adjust));
                            } else {
                                borrowValue.value = borrowValue.value.add(Decimal.mul(assetValue, adjust));
                            }
                        }
                
                        return (supplyValue, borrowValue);
                    }
                
                    function isCollateralized(
                        Storage.State storage state,
                        Account.Info memory account,
                        Cache.MarketCache memory cache,
                        bool requireMinBorrow
                    )
                        internal
                        view
                        returns (bool)
                    {
                        // get account values (adjusted for liquidity)
                        (
                            Monetary.Value memory supplyValue,
                            Monetary.Value memory borrowValue
                        ) = state.getAccountValues(account, cache, /* adjustForLiquidity = */ true);
                
                        if (borrowValue.value == 0) {
                            return true;
                        }
                
                        if (requireMinBorrow) {
                            Require.that(
                                borrowValue.value >= state.riskParams.minBorrowedValue.value,
                                FILE,
                                "Borrow value too low",
                                account.owner,
                                account.number,
                                borrowValue.value
                            );
                        }
                
                        uint256 requiredMargin = Decimal.mul(borrowValue.value, state.riskParams.marginRatio);
                
                        return supplyValue.value >= borrowValue.value.add(requiredMargin);
                    }
                
                    function isGlobalOperator(
                        Storage.State storage state,
                        address operator
                    )
                        internal
                        view
                        returns (bool)
                    {
                        return state.globalOperators[operator];
                    }
                
                    function isLocalOperator(
                        Storage.State storage state,
                        address owner,
                        address operator
                    )
                        internal
                        view
                        returns (bool)
                    {
                        return state.operators[owner][operator];
                    }
                
                    function requireIsOperator(
                        Storage.State storage state,
                        Account.Info memory account,
                        address operator
                    )
                        internal
                        view
                    {
                        bool isValidOperator =
                            operator == account.owner
                            || state.isGlobalOperator(operator)
                            || state.isLocalOperator(account.owner, operator);
                
                        Require.that(
                            isValidOperator,
                            FILE,
                            "Unpermissioned operator",
                            operator
                        );
                    }
                
                    /**
                     * Determine and set an account's balance based on the intended balance change. Return the
                     * equivalent amount in wei
                     */
                    function getNewParAndDeltaWei(
                        Storage.State storage state,
                        Account.Info memory account,
                        uint256 marketId,
                        Types.AssetAmount memory amount
                    )
                        internal
                        view
                        returns (Types.Par memory, Types.Wei memory)
                    {
                        Types.Par memory oldPar = state.getPar(account, marketId);
                
                        if (amount.value == 0 && amount.ref == Types.AssetReference.Delta) {
                            return (oldPar, Types.zeroWei());
                        }
                
                        Interest.Index memory index = state.getIndex(marketId);
                        Types.Wei memory oldWei = Interest.parToWei(oldPar, index);
                        Types.Par memory newPar;
                        Types.Wei memory deltaWei;
                
                        if (amount.denomination == Types.AssetDenomination.Wei) {
                            deltaWei = Types.Wei({
                                sign: amount.sign,
                                value: amount.value
                            });
                            if (amount.ref == Types.AssetReference.Target) {
                                deltaWei = deltaWei.sub(oldWei);
                            }
                            newPar = Interest.weiToPar(oldWei.add(deltaWei), index);
                        } else { // AssetDenomination.Par
                            newPar = Types.Par({
                                sign: amount.sign,
                                value: amount.value.to128()
                            });
                            if (amount.ref == Types.AssetReference.Delta) {
                                newPar = oldPar.add(newPar);
                            }
                            deltaWei = Interest.parToWei(newPar, index).sub(oldWei);
                        }
                
                        return (newPar, deltaWei);
                    }
                
                    function getNewParAndDeltaWeiForLiquidation(
                        Storage.State storage state,
                        Account.Info memory account,
                        uint256 marketId,
                        Types.AssetAmount memory amount
                    )
                        internal
                        view
                        returns (Types.Par memory, Types.Wei memory)
                    {
                        Types.Par memory oldPar = state.getPar(account, marketId);
                
                        Require.that(
                            !oldPar.isPositive(),
                            FILE,
                            "Owed balance cannot be positive",
                            account.owner,
                            account.number,
                            marketId
                        );
                
                        (
                            Types.Par memory newPar,
                            Types.Wei memory deltaWei
                        ) = state.getNewParAndDeltaWei(
                            account,
                            marketId,
                            amount
                        );
                
                        // if attempting to over-repay the owed asset, bound it by the maximum
                        if (newPar.isPositive()) {
                            newPar = Types.zeroPar();
                            deltaWei = state.getWei(account, marketId).negative();
                        }
                
                        Require.that(
                            !deltaWei.isNegative() && oldPar.value >= newPar.value,
                            FILE,
                            "Owed balance cannot increase",
                            account.owner,
                            account.number,
                            marketId
                        );
                
                        // if not paying back enough wei to repay any par, then bound wei to zero
                        if (oldPar.equals(newPar)) {
                            deltaWei = Types.zeroWei();
                        }
                
                        return (newPar, deltaWei);
                    }
                
                    function isVaporizable(
                        Storage.State storage state,
                        Account.Info memory account,
                        Cache.MarketCache memory cache
                    )
                        internal
                        view
                        returns (bool)
                    {
                        bool hasNegative = false;
                        uint256 numMarkets = cache.getNumMarkets();
                        for (uint256 m = 0; m < numMarkets; m++) {
                            if (!cache.hasMarket(m)) {
                                continue;
                            }
                            Types.Par memory par = state.getPar(account, m);
                            if (par.isZero()) {
                                continue;
                            } else if (par.sign) {
                                return false;
                            } else {
                                hasNegative = true;
                            }
                        }
                        return hasNegative;
                    }
                
                    // =============== Setter Functions ===============
                
                    function updateIndex(
                        Storage.State storage state,
                        uint256 marketId
                    )
                        internal
                        returns (Interest.Index memory)
                    {
                        Interest.Index memory index = state.getIndex(marketId);
                        if (index.lastUpdate == Time.currentTime()) {
                            return index;
                        }
                        return state.markets[marketId].index = state.fetchNewIndex(marketId, index);
                    }
                
                    function setStatus(
                        Storage.State storage state,
                        Account.Info memory account,
                        Account.Status status
                    )
                        internal
                    {
                        state.accounts[account.owner][account.number].status = status;
                    }
                
                    function setPar(
                        Storage.State storage state,
                        Account.Info memory account,
                        uint256 marketId,
                        Types.Par memory newPar
                    )
                        internal
                    {
                        Types.Par memory oldPar = state.getPar(account, marketId);
                
                        if (Types.equals(oldPar, newPar)) {
                            return;
                        }
                
                        // updateTotalPar
                        Types.TotalPar memory totalPar = state.getTotalPar(marketId);
                
                        // roll-back oldPar
                        if (oldPar.sign) {
                            totalPar.supply = uint256(totalPar.supply).sub(oldPar.value).to128();
                        } else {
                            totalPar.borrow = uint256(totalPar.borrow).sub(oldPar.value).to128();
                        }
                
                        // roll-forward newPar
                        if (newPar.sign) {
                            totalPar.supply = uint256(totalPar.supply).add(newPar.value).to128();
                        } else {
                            totalPar.borrow = uint256(totalPar.borrow).add(newPar.value).to128();
                        }
                
                        state.markets[marketId].totalPar = totalPar;
                        state.accounts[account.owner][account.number].balances[marketId] = newPar;
                    }
                
                    /**
                     * Determine and set an account's balance based on a change in wei
                     */
                    function setParFromDeltaWei(
                        Storage.State storage state,
                        Account.Info memory account,
                        uint256 marketId,
                        Types.Wei memory deltaWei
                    )
                        internal
                    {
                        if (deltaWei.isZero()) {
                            return;
                        }
                        Interest.Index memory index = state.getIndex(marketId);
                        Types.Wei memory oldWei = state.getWei(account, marketId);
                        Types.Wei memory newWei = oldWei.add(deltaWei);
                        Types.Par memory newPar = Interest.weiToPar(newWei, index);
                        state.setPar(
                            account,
                            marketId,
                            newPar
                        );
                    }
                }
                
                // File: contracts/protocol/lib/Events.sol
                
                /**
                 * @title Events
                 * @author dYdX
                 *
                 * Library to parse and emit logs from which the state of all accounts and indexes can be followed
                 */
                library Events {
                    using Types for Types.Wei;
                    using Storage for Storage.State;
                
                    // ============ Events ============
                
                    event LogIndexUpdate(
                        uint256 indexed market,
                        Interest.Index index
                    );
                
                    event LogOperation(
                        address sender
                    );
                
                    event LogDeposit(
                        address indexed accountOwner,
                        uint256 accountNumber,
                        uint256 market,
                        BalanceUpdate update,
                        address from
                    );
                
                    event LogWithdraw(
                        address indexed accountOwner,
                        uint256 accountNumber,
                        uint256 market,
                        BalanceUpdate update,
                        address to
                    );
                
                    event LogTransfer(
                        address indexed accountOneOwner,
                        uint256 accountOneNumber,
                        address indexed accountTwoOwner,
                        uint256 accountTwoNumber,
                        uint256 market,
                        BalanceUpdate updateOne,
                        BalanceUpdate updateTwo
                    );
                
                    event LogBuy(
                        address indexed accountOwner,
                        uint256 accountNumber,
                        uint256 takerMarket,
                        uint256 makerMarket,
                        BalanceUpdate takerUpdate,
                        BalanceUpdate makerUpdate,
                        address exchangeWrapper
                    );
                
                    event LogSell(
                        address indexed accountOwner,
                        uint256 accountNumber,
                        uint256 takerMarket,
                        uint256 makerMarket,
                        BalanceUpdate takerUpdate,
                        BalanceUpdate makerUpdate,
                        address exchangeWrapper
                    );
                
                    event LogTrade(
                        address indexed takerAccountOwner,
                        uint256 takerAccountNumber,
                        address indexed makerAccountOwner,
                        uint256 makerAccountNumber,
                        uint256 inputMarket,
                        uint256 outputMarket,
                        BalanceUpdate takerInputUpdate,
                        BalanceUpdate takerOutputUpdate,
                        BalanceUpdate makerInputUpdate,
                        BalanceUpdate makerOutputUpdate,
                        address autoTrader
                    );
                
                    event LogCall(
                        address indexed accountOwner,
                        uint256 accountNumber,
                        address callee
                    );
                
                    event LogLiquidate(
                        address indexed solidAccountOwner,
                        uint256 solidAccountNumber,
                        address indexed liquidAccountOwner,
                        uint256 liquidAccountNumber,
                        uint256 heldMarket,
                        uint256 owedMarket,
                        BalanceUpdate solidHeldUpdate,
                        BalanceUpdate solidOwedUpdate,
                        BalanceUpdate liquidHeldUpdate,
                        BalanceUpdate liquidOwedUpdate
                    );
                
                    event LogVaporize(
                        address indexed solidAccountOwner,
                        uint256 solidAccountNumber,
                        address indexed vaporAccountOwner,
                        uint256 vaporAccountNumber,
                        uint256 heldMarket,
                        uint256 owedMarket,
                        BalanceUpdate solidHeldUpdate,
                        BalanceUpdate solidOwedUpdate,
                        BalanceUpdate vaporOwedUpdate
                    );
                
                    // ============ Structs ============
                
                    struct BalanceUpdate {
                        Types.Wei deltaWei;
                        Types.Par newPar;
                    }
                
                    // ============ Internal Functions ============
                
                    function logIndexUpdate(
                        uint256 marketId,
                        Interest.Index memory index
                    )
                        internal
                    {
                        emit LogIndexUpdate(
                            marketId,
                            index
                        );
                    }
                
                    function logOperation()
                        internal
                    {
                        emit LogOperation(msg.sender);
                    }
                
                    function logDeposit(
                        Storage.State storage state,
                        Actions.DepositArgs memory args,
                        Types.Wei memory deltaWei
                    )
                        internal
                    {
                        emit LogDeposit(
                            args.account.owner,
                            args.account.number,
                            args.market,
                            getBalanceUpdate(
                                state,
                                args.account,
                                args.market,
                                deltaWei
                            ),
                            args.from
                        );
                    }
                
                    function logWithdraw(
                        Storage.State storage state,
                        Actions.WithdrawArgs memory args,
                        Types.Wei memory deltaWei
                    )
                        internal
                    {
                        emit LogWithdraw(
                            args.account.owner,
                            args.account.number,
                            args.market,
                            getBalanceUpdate(
                                state,
                                args.account,
                                args.market,
                                deltaWei
                            ),
                            args.to
                        );
                    }
                
                    function logTransfer(
                        Storage.State storage state,
                        Actions.TransferArgs memory args,
                        Types.Wei memory deltaWei
                    )
                        internal
                    {
                        emit LogTransfer(
                            args.accountOne.owner,
                            args.accountOne.number,
                            args.accountTwo.owner,
                            args.accountTwo.number,
                            args.market,
                            getBalanceUpdate(
                                state,
                                args.accountOne,
                                args.market,
                                deltaWei
                            ),
                            getBalanceUpdate(
                                state,
                                args.accountTwo,
                                args.market,
                                deltaWei.negative()
                            )
                        );
                    }
                
                    function logBuy(
                        Storage.State storage state,
                        Actions.BuyArgs memory args,
                        Types.Wei memory takerWei,
                        Types.Wei memory makerWei
                    )
                        internal
                    {
                        emit LogBuy(
                            args.account.owner,
                            args.account.number,
                            args.takerMarket,
                            args.makerMarket,
                            getBalanceUpdate(
                                state,
                                args.account,
                                args.takerMarket,
                                takerWei
                            ),
                            getBalanceUpdate(
                                state,
                                args.account,
                                args.makerMarket,
                                makerWei
                            ),
                            args.exchangeWrapper
                        );
                    }
                
                    function logSell(
                        Storage.State storage state,
                        Actions.SellArgs memory args,
                        Types.Wei memory takerWei,
                        Types.Wei memory makerWei
                    )
                        internal
                    {
                        emit LogSell(
                            args.account.owner,
                            args.account.number,
                            args.takerMarket,
                            args.makerMarket,
                            getBalanceUpdate(
                                state,
                                args.account,
                                args.takerMarket,
                                takerWei
                            ),
                            getBalanceUpdate(
                                state,
                                args.account,
                                args.makerMarket,
                                makerWei
                            ),
                            args.exchangeWrapper
                        );
                    }
                
                    function logTrade(
                        Storage.State storage state,
                        Actions.TradeArgs memory args,
                        Types.Wei memory inputWei,
                        Types.Wei memory outputWei
                    )
                        internal
                    {
                        BalanceUpdate[4] memory updates = [
                            getBalanceUpdate(
                                state,
                                args.takerAccount,
                                args.inputMarket,
                                inputWei.negative()
                            ),
                            getBalanceUpdate(
                                state,
                                args.takerAccount,
                                args.outputMarket,
                                outputWei.negative()
                            ),
                            getBalanceUpdate(
                                state,
                                args.makerAccount,
                                args.inputMarket,
                                inputWei
                            ),
                            getBalanceUpdate(
                                state,
                                args.makerAccount,
                                args.outputMarket,
                                outputWei
                            )
                        ];
                
                        emit LogTrade(
                            args.takerAccount.owner,
                            args.takerAccount.number,
                            args.makerAccount.owner,
                            args.makerAccount.number,
                            args.inputMarket,
                            args.outputMarket,
                            updates[0],
                            updates[1],
                            updates[2],
                            updates[3],
                            args.autoTrader
                        );
                    }
                
                    function logCall(
                        Actions.CallArgs memory args
                    )
                        internal
                    {
                        emit LogCall(
                            args.account.owner,
                            args.account.number,
                            args.callee
                        );
                    }
                
                    function logLiquidate(
                        Storage.State storage state,
                        Actions.LiquidateArgs memory args,
                        Types.Wei memory heldWei,
                        Types.Wei memory owedWei
                    )
                        internal
                    {
                        BalanceUpdate memory solidHeldUpdate = getBalanceUpdate(
                            state,
                            args.solidAccount,
                            args.heldMarket,
                            heldWei.negative()
                        );
                        BalanceUpdate memory solidOwedUpdate = getBalanceUpdate(
                            state,
                            args.solidAccount,
                            args.owedMarket,
                            owedWei.negative()
                        );
                        BalanceUpdate memory liquidHeldUpdate = getBalanceUpdate(
                            state,
                            args.liquidAccount,
                            args.heldMarket,
                            heldWei
                        );
                        BalanceUpdate memory liquidOwedUpdate = getBalanceUpdate(
                            state,
                            args.liquidAccount,
                            args.owedMarket,
                            owedWei
                        );
                
                        emit LogLiquidate(
                            args.solidAccount.owner,
                            args.solidAccount.number,
                            args.liquidAccount.owner,
                            args.liquidAccount.number,
                            args.heldMarket,
                            args.owedMarket,
                            solidHeldUpdate,
                            solidOwedUpdate,
                            liquidHeldUpdate,
                            liquidOwedUpdate
                        );
                    }
                
                    function logVaporize(
                        Storage.State storage state,
                        Actions.VaporizeArgs memory args,
                        Types.Wei memory heldWei,
                        Types.Wei memory owedWei,
                        Types.Wei memory excessWei
                    )
                        internal
                    {
                        BalanceUpdate memory solidHeldUpdate = getBalanceUpdate(
                            state,
                            args.solidAccount,
                            args.heldMarket,
                            heldWei.negative()
                        );
                        BalanceUpdate memory solidOwedUpdate = getBalanceUpdate(
                            state,
                            args.solidAccount,
                            args.owedMarket,
                            owedWei.negative()
                        );
                        BalanceUpdate memory vaporOwedUpdate = getBalanceUpdate(
                            state,
                            args.vaporAccount,
                            args.owedMarket,
                            owedWei.add(excessWei)
                        );
                
                        emit LogVaporize(
                            args.solidAccount.owner,
                            args.solidAccount.number,
                            args.vaporAccount.owner,
                            args.vaporAccount.number,
                            args.heldMarket,
                            args.owedMarket,
                            solidHeldUpdate,
                            solidOwedUpdate,
                            vaporOwedUpdate
                        );
                    }
                
                    // ============ Private Functions ============
                
                    function getBalanceUpdate(
                        Storage.State storage state,
                        Account.Info memory account,
                        uint256 market,
                        Types.Wei memory deltaWei
                    )
                        private
                        view
                        returns (BalanceUpdate memory)
                    {
                        return BalanceUpdate({
                            deltaWei: deltaWei,
                            newPar: state.getPar(account, market)
                        });
                    }
                }
                
                // File: contracts/protocol/interfaces/IExchangeWrapper.sol
                
                /**
                 * @title IExchangeWrapper
                 * @author dYdX
                 *
                 * Interface that Exchange Wrappers for Solo must implement in order to trade ERC20 tokens.
                 */
                interface IExchangeWrapper {
                
                    // ============ Public Functions ============
                
                    /**
                     * Exchange some amount of takerToken for makerToken.
                     *
                     * @param  tradeOriginator      Address of the initiator of the trade (however, this value
                     *                              cannot always be trusted as it is set at the discretion of the
                     *                              msg.sender)
                     * @param  receiver             Address to set allowance on once the trade has completed
                     * @param  makerToken           Address of makerToken, the token to receive
                     * @param  takerToken           Address of takerToken, the token to pay
                     * @param  requestedFillAmount  Amount of takerToken being paid
                     * @param  orderData            Arbitrary bytes data for any information to pass to the exchange
                     * @return                      The amount of makerToken received
                     */
                    function exchange(
                        address tradeOriginator,
                        address receiver,
                        address makerToken,
                        address takerToken,
                        uint256 requestedFillAmount,
                        bytes calldata orderData
                    )
                        external
                        returns (uint256);
                
                    /**
                     * Get amount of takerToken required to buy a certain amount of makerToken for a given trade.
                     * Should match the takerToken amount used in exchangeForAmount. If the order cannot provide
                     * exactly desiredMakerToken, then it must return the price to buy the minimum amount greater
                     * than desiredMakerToken
                     *
                     * @param  makerToken         Address of makerToken, the token to receive
                     * @param  takerToken         Address of takerToken, the token to pay
                     * @param  desiredMakerToken  Amount of makerToken requested
                     * @param  orderData          Arbitrary bytes data for any information to pass to the exchange
                     * @return                    Amount of takerToken the needed to complete the exchange
                     */
                    function getExchangeCost(
                        address makerToken,
                        address takerToken,
                        uint256 desiredMakerToken,
                        bytes calldata orderData
                    )
                        external
                        view
                        returns (uint256);
                }
                
                // File: contracts/protocol/lib/Exchange.sol
                
                /**
                 * @title Exchange
                 * @author dYdX
                 *
                 * Library for transferring tokens and interacting with ExchangeWrappers by using the Wei struct
                 */
                library Exchange {
                    using Types for Types.Wei;
                
                    // ============ Constants ============
                
                    bytes32 constant FILE = "Exchange";
                
                    // ============ Library Functions ============
                
                    function transferOut(
                        address token,
                        address to,
                        Types.Wei memory deltaWei
                    )
                        internal
                    {
                        Require.that(
                            !deltaWei.isPositive(),
                            FILE,
                            "Cannot transferOut positive",
                            deltaWei.value
                        );
                
                        Token.transfer(
                            token,
                            to,
                            deltaWei.value
                        );
                    }
                
                    function transferIn(
                        address token,
                        address from,
                        Types.Wei memory deltaWei
                    )
                        internal
                    {
                        Require.that(
                            !deltaWei.isNegative(),
                            FILE,
                            "Cannot transferIn negative",
                            deltaWei.value
                        );
                
                        Token.transferFrom(
                            token,
                            from,
                            address(this),
                            deltaWei.value
                        );
                    }
                
                    function getCost(
                        address exchangeWrapper,
                        address supplyToken,
                        address borrowToken,
                        Types.Wei memory desiredAmount,
                        bytes memory orderData
                    )
                        internal
                        view
                        returns (Types.Wei memory)
                    {
                        Require.that(
                            !desiredAmount.isNegative(),
                            FILE,
                            "Cannot getCost negative",
                            desiredAmount.value
                        );
                
                        Types.Wei memory result;
                        result.sign = false;
                        result.value = IExchangeWrapper(exchangeWrapper).getExchangeCost(
                            supplyToken,
                            borrowToken,
                            desiredAmount.value,
                            orderData
                        );
                
                        return result;
                    }
                
                    function exchange(
                        address exchangeWrapper,
                        address accountOwner,
                        address supplyToken,
                        address borrowToken,
                        Types.Wei memory requestedFillAmount,
                        bytes memory orderData
                    )
                        internal
                        returns (Types.Wei memory)
                    {
                        Require.that(
                            !requestedFillAmount.isPositive(),
                            FILE,
                            "Cannot exchange positive",
                            requestedFillAmount.value
                        );
                
                        transferOut(borrowToken, exchangeWrapper, requestedFillAmount);
                
                        Types.Wei memory result;
                        result.sign = true;
                        result.value = IExchangeWrapper(exchangeWrapper).exchange(
                            accountOwner,
                            address(this),
                            supplyToken,
                            borrowToken,
                            requestedFillAmount.value,
                            orderData
                        );
                
                        transferIn(supplyToken, exchangeWrapper, result);
                
                        return result;
                    }
                }
                
                // File: contracts/protocol/impl/OperationImpl.sol
                
                /**
                 * @title OperationImpl
                 * @author dYdX
                 *
                 * Logic for processing actions
                 */
                library OperationImpl {
                    using Cache for Cache.MarketCache;
                    using SafeMath for uint256;
                    using Storage for Storage.State;
                    using Types for Types.Par;
                    using Types for Types.Wei;
                
                    // ============ Constants ============
                
                    bytes32 constant FILE = "OperationImpl";
                
                    // ============ Public Functions ============
                
                    function operate(
                        Storage.State storage state,
                        Account.Info[] memory accounts,
                        Actions.ActionArgs[] memory actions
                    )
                        public
                    {
                        Events.logOperation();
                
                        _verifyInputs(accounts, actions);
                
                        (
                            bool[] memory primaryAccounts,
                            Cache.MarketCache memory cache
                        ) = _runPreprocessing(
                            state,
                            accounts,
                            actions
                        );
                
                        _runActions(
                            state,
                            accounts,
                            actions,
                            cache
                        );
                
                        _verifyFinalState(
                            state,
                            accounts,
                            primaryAccounts,
                            cache
                        );
                    }
                
                    // ============ Helper Functions ============
                
                    function _verifyInputs(
                        Account.Info[] memory accounts,
                        Actions.ActionArgs[] memory actions
                    )
                        private
                        pure
                    {
                        Require.that(
                            actions.length != 0,
                            FILE,
                            "Cannot have zero actions"
                        );
                
                        Require.that(
                            accounts.length != 0,
                            FILE,
                            "Cannot have zero accounts"
                        );
                
                        for (uint256 a = 0; a < accounts.length; a++) {
                            for (uint256 b = a + 1; b < accounts.length; b++) {
                                Require.that(
                                    !Account.equals(accounts[a], accounts[b]),
                                    FILE,
                                    "Cannot duplicate accounts",
                                    a,
                                    b
                                );
                            }
                        }
                    }
                
                    function _runPreprocessing(
                        Storage.State storage state,
                        Account.Info[] memory accounts,
                        Actions.ActionArgs[] memory actions
                    )
                        private
                        returns (
                            bool[] memory,
                            Cache.MarketCache memory
                        )
                    {
                        uint256 numMarkets = state.numMarkets;
                        bool[] memory primaryAccounts = new bool[](accounts.length);
                        Cache.MarketCache memory cache = Cache.create(numMarkets);
                
                        // keep track of primary accounts and indexes that need updating
                        for (uint256 i = 0; i < actions.length; i++) {
                            Actions.ActionArgs memory arg = actions[i];
                            Actions.ActionType actionType = arg.actionType;
                            Actions.MarketLayout marketLayout = Actions.getMarketLayout(actionType);
                            Actions.AccountLayout accountLayout = Actions.getAccountLayout(actionType);
                
                            // parse out primary accounts
                            if (accountLayout != Actions.AccountLayout.OnePrimary) {
                                Require.that(
                                    arg.accountId != arg.otherAccountId,
                                    FILE,
                                    "Duplicate accounts in action",
                                    i
                                );
                                if (accountLayout == Actions.AccountLayout.TwoPrimary) {
                                    primaryAccounts[arg.otherAccountId] = true;
                                } else {
                                    assert(accountLayout == Actions.AccountLayout.PrimaryAndSecondary);
                                    Require.that(
                                        !primaryAccounts[arg.otherAccountId],
                                        FILE,
                                        "Requires non-primary account",
                                        arg.otherAccountId
                                    );
                                }
                            }
                            primaryAccounts[arg.accountId] = true;
                
                            // keep track of indexes to update
                            if (marketLayout == Actions.MarketLayout.OneMarket) {
                                _updateMarket(state, cache, arg.primaryMarketId);
                            } else if (marketLayout == Actions.MarketLayout.TwoMarkets) {
                                Require.that(
                                    arg.primaryMarketId != arg.secondaryMarketId,
                                    FILE,
                                    "Duplicate markets in action",
                                    i
                                );
                                _updateMarket(state, cache, arg.primaryMarketId);
                                _updateMarket(state, cache, arg.secondaryMarketId);
                            } else {
                                assert(marketLayout == Actions.MarketLayout.ZeroMarkets);
                            }
                        }
                
                        // get any other markets for which an account has a balance
                        for (uint256 m = 0; m < numMarkets; m++) {
                            if (cache.hasMarket(m)) {
                                continue;
                            }
                            for (uint256 a = 0; a < accounts.length; a++) {
                                if (!state.getPar(accounts[a], m).isZero()) {
                                    _updateMarket(state, cache, m);
                                    break;
                                }
                            }
                        }
                
                        return (primaryAccounts, cache);
                    }
                
                    function _updateMarket(
                        Storage.State storage state,
                        Cache.MarketCache memory cache,
                        uint256 marketId
                    )
                        private
                    {
                        bool updated = cache.addMarket(state, marketId);
                        if (updated) {
                            Events.logIndexUpdate(marketId, state.updateIndex(marketId));
                        }
                    }
                
                    function _runActions(
                        Storage.State storage state,
                        Account.Info[] memory accounts,
                        Actions.ActionArgs[] memory actions,
                        Cache.MarketCache memory cache
                    )
                        private
                    {
                        for (uint256 i = 0; i < actions.length; i++) {
                            Actions.ActionArgs memory action = actions[i];
                            Actions.ActionType actionType = action.actionType;
                
                            if (actionType == Actions.ActionType.Deposit) {
                                _deposit(state, Actions.parseDepositArgs(accounts, action));
                            }
                            else if (actionType == Actions.ActionType.Withdraw) {
                                _withdraw(state, Actions.parseWithdrawArgs(accounts, action));
                            }
                            else if (actionType == Actions.ActionType.Transfer) {
                                _transfer(state, Actions.parseTransferArgs(accounts, action));
                            }
                            else if (actionType == Actions.ActionType.Buy) {
                                _buy(state, Actions.parseBuyArgs(accounts, action));
                            }
                            else if (actionType == Actions.ActionType.Sell) {
                                _sell(state, Actions.parseSellArgs(accounts, action));
                            }
                            else if (actionType == Actions.ActionType.Trade) {
                                _trade(state, Actions.parseTradeArgs(accounts, action));
                            }
                            else if (actionType == Actions.ActionType.Liquidate) {
                                _liquidate(state, Actions.parseLiquidateArgs(accounts, action), cache);
                            }
                            else if (actionType == Actions.ActionType.Vaporize) {
                                _vaporize(state, Actions.parseVaporizeArgs(accounts, action), cache);
                            }
                            else  {
                                assert(actionType == Actions.ActionType.Call);
                                _call(state, Actions.parseCallArgs(accounts, action));
                            }
                        }
                    }
                
                    function _verifyFinalState(
                        Storage.State storage state,
                        Account.Info[] memory accounts,
                        bool[] memory primaryAccounts,
                        Cache.MarketCache memory cache
                    )
                        private
                    {
                        // verify no increase in borrowPar for closing markets
                        uint256 numMarkets = cache.getNumMarkets();
                        for (uint256 m = 0; m < numMarkets; m++) {
                            if (cache.getIsClosing(m)) {
                                Require.that(
                                    state.getTotalPar(m).borrow <= cache.getBorrowPar(m),
                                    FILE,
                                    "Market is closing",
                                    m
                                );
                            }
                        }
                
                        // verify account collateralization
                        for (uint256 a = 0; a < accounts.length; a++) {
                            Account.Info memory account = accounts[a];
                
                            // validate minBorrowedValue
                            bool collateralized = state.isCollateralized(account, cache, true);
                
                            // don't check collateralization for non-primary accounts
                            if (!primaryAccounts[a]) {
                                continue;
                            }
                
                            // check collateralization for primary accounts
                            Require.that(
                                collateralized,
                                FILE,
                                "Undercollateralized account",
                                account.owner,
                                account.number
                            );
                
                            // ensure status is normal for primary accounts
                            if (state.getStatus(account) != Account.Status.Normal) {
                                state.setStatus(account, Account.Status.Normal);
                            }
                        }
                    }
                
                    // ============ Action Functions ============
                
                    function _deposit(
                        Storage.State storage state,
                        Actions.DepositArgs memory args
                    )
                        private
                    {
                        state.requireIsOperator(args.account, msg.sender);
                
                        Require.that(
                            args.from == msg.sender || args.from == args.account.owner,
                            FILE,
                            "Invalid deposit source",
                            args.from
                        );
                
                        (
                            Types.Par memory newPar,
                            Types.Wei memory deltaWei
                        ) = state.getNewParAndDeltaWei(
                            args.account,
                            args.market,
                            args.amount
                        );
                
                        state.setPar(
                            args.account,
                            args.market,
                            newPar
                        );
                
                        // requires a positive deltaWei
                        Exchange.transferIn(
                            state.getToken(args.market),
                            args.from,
                            deltaWei
                        );
                
                        Events.logDeposit(
                            state,
                            args,
                            deltaWei
                        );
                    }
                
                    function _withdraw(
                        Storage.State storage state,
                        Actions.WithdrawArgs memory args
                    )
                        private
                    {
                        state.requireIsOperator(args.account, msg.sender);
                
                        (
                            Types.Par memory newPar,
                            Types.Wei memory deltaWei
                        ) = state.getNewParAndDeltaWei(
                            args.account,
                            args.market,
                            args.amount
                        );
                
                        state.setPar(
                            args.account,
                            args.market,
                            newPar
                        );
                
                        // requires a negative deltaWei
                        Exchange.transferOut(
                            state.getToken(args.market),
                            args.to,
                            deltaWei
                        );
                
                        Events.logWithdraw(
                            state,
                            args,
                            deltaWei
                        );
                    }
                
                    function _transfer(
                        Storage.State storage state,
                        Actions.TransferArgs memory args
                    )
                        private
                    {
                        state.requireIsOperator(args.accountOne, msg.sender);
                        state.requireIsOperator(args.accountTwo, msg.sender);
                
                        (
                            Types.Par memory newPar,
                            Types.Wei memory deltaWei
                        ) = state.getNewParAndDeltaWei(
                            args.accountOne,
                            args.market,
                            args.amount
                        );
                
                        state.setPar(
                            args.accountOne,
                            args.market,
                            newPar
                        );
                
                        state.setParFromDeltaWei(
                            args.accountTwo,
                            args.market,
                            deltaWei.negative()
                        );
                
                        Events.logTransfer(
                            state,
                            args,
                            deltaWei
                        );
                    }
                
                    function _buy(
                        Storage.State storage state,
                        Actions.BuyArgs memory args
                    )
                        private
                    {
                        state.requireIsOperator(args.account, msg.sender);
                
                        address takerToken = state.getToken(args.takerMarket);
                        address makerToken = state.getToken(args.makerMarket);
                
                        (
                            Types.Par memory makerPar,
                            Types.Wei memory makerWei
                        ) = state.getNewParAndDeltaWei(
                            args.account,
                            args.makerMarket,
                            args.amount
                        );
                
                        Types.Wei memory takerWei = Exchange.getCost(
                            args.exchangeWrapper,
                            makerToken,
                            takerToken,
                            makerWei,
                            args.orderData
                        );
                
                        Types.Wei memory tokensReceived = Exchange.exchange(
                            args.exchangeWrapper,
                            args.account.owner,
                            makerToken,
                            takerToken,
                            takerWei,
                            args.orderData
                        );
                
                        Require.that(
                            tokensReceived.value >= makerWei.value,
                            FILE,
                            "Buy amount less than promised",
                            tokensReceived.value,
                            makerWei.value
                        );
                
                        state.setPar(
                            args.account,
                            args.makerMarket,
                            makerPar
                        );
                
                        state.setParFromDeltaWei(
                            args.account,
                            args.takerMarket,
                            takerWei
                        );
                
                        Events.logBuy(
                            state,
                            args,
                            takerWei,
                            makerWei
                        );
                    }
                
                    function _sell(
                        Storage.State storage state,
                        Actions.SellArgs memory args
                    )
                        private
                    {
                        state.requireIsOperator(args.account, msg.sender);
                
                        address takerToken = state.getToken(args.takerMarket);
                        address makerToken = state.getToken(args.makerMarket);
                
                        (
                            Types.Par memory takerPar,
                            Types.Wei memory takerWei
                        ) = state.getNewParAndDeltaWei(
                            args.account,
                            args.takerMarket,
                            args.amount
                        );
                
                        Types.Wei memory makerWei = Exchange.exchange(
                            args.exchangeWrapper,
                            args.account.owner,
                            makerToken,
                            takerToken,
                            takerWei,
                            args.orderData
                        );
                
                        state.setPar(
                            args.account,
                            args.takerMarket,
                            takerPar
                        );
                
                        state.setParFromDeltaWei(
                            args.account,
                            args.makerMarket,
                            makerWei
                        );
                
                        Events.logSell(
                            state,
                            args,
                            takerWei,
                            makerWei
                        );
                    }
                
                    function _trade(
                        Storage.State storage state,
                        Actions.TradeArgs memory args
                    )
                        private
                    {
                        state.requireIsOperator(args.takerAccount, msg.sender);
                        state.requireIsOperator(args.makerAccount, args.autoTrader);
                
                        Types.Par memory oldInputPar = state.getPar(
                            args.makerAccount,
                            args.inputMarket
                        );
                        (
                            Types.Par memory newInputPar,
                            Types.Wei memory inputWei
                        ) = state.getNewParAndDeltaWei(
                            args.makerAccount,
                            args.inputMarket,
                            args.amount
                        );
                
                        Types.AssetAmount memory outputAmount = IAutoTrader(args.autoTrader).getTradeCost(
                            args.inputMarket,
                            args.outputMarket,
                            args.makerAccount,
                            args.takerAccount,
                            oldInputPar,
                            newInputPar,
                            inputWei,
                            args.tradeData
                        );
                
                        (
                            Types.Par memory newOutputPar,
                            Types.Wei memory outputWei
                        ) = state.getNewParAndDeltaWei(
                            args.makerAccount,
                            args.outputMarket,
                            outputAmount
                        );
                
                        Require.that(
                            outputWei.isZero() || inputWei.isZero() || outputWei.sign != inputWei.sign,
                            FILE,
                            "Trades cannot be one-sided"
                        );
                
                        // set the balance for the maker
                        state.setPar(
                            args.makerAccount,
                            args.inputMarket,
                            newInputPar
                        );
                        state.setPar(
                            args.makerAccount,
                            args.outputMarket,
                            newOutputPar
                        );
                
                        // set the balance for the taker
                        state.setParFromDeltaWei(
                            args.takerAccount,
                            args.inputMarket,
                            inputWei.negative()
                        );
                        state.setParFromDeltaWei(
                            args.takerAccount,
                            args.outputMarket,
                            outputWei.negative()
                        );
                
                        Events.logTrade(
                            state,
                            args,
                            inputWei,
                            outputWei
                        );
                    }
                
                    function _liquidate(
                        Storage.State storage state,
                        Actions.LiquidateArgs memory args,
                        Cache.MarketCache memory cache
                    )
                        private
                    {
                        state.requireIsOperator(args.solidAccount, msg.sender);
                
                        // verify liquidatable
                        if (Account.Status.Liquid != state.getStatus(args.liquidAccount)) {
                            Require.that(
                                !state.isCollateralized(args.liquidAccount, cache, /* requireMinBorrow = */ false),
                                FILE,
                                "Unliquidatable account",
                                args.liquidAccount.owner,
                                args.liquidAccount.number
                            );
                            state.setStatus(args.liquidAccount, Account.Status.Liquid);
                        }
                
                        Types.Wei memory maxHeldWei = state.getWei(
                            args.liquidAccount,
                            args.heldMarket
                        );
                
                        Require.that(
                            !maxHeldWei.isNegative(),
                            FILE,
                            "Collateral cannot be negative",
                            args.liquidAccount.owner,
                            args.liquidAccount.number,
                            args.heldMarket
                        );
                
                        (
                            Types.Par memory owedPar,
                            Types.Wei memory owedWei
                        ) = state.getNewParAndDeltaWeiForLiquidation(
                            args.liquidAccount,
                            args.owedMarket,
                            args.amount
                        );
                
                        (
                            Monetary.Price memory heldPrice,
                            Monetary.Price memory owedPrice
                        ) = _getLiquidationPrices(
                            state,
                            cache,
                            args.heldMarket,
                            args.owedMarket
                        );
                
                        Types.Wei memory heldWei = _owedWeiToHeldWei(owedWei, heldPrice, owedPrice);
                
                        // if attempting to over-borrow the held asset, bound it by the maximum
                        if (heldWei.value > maxHeldWei.value) {
                            heldWei = maxHeldWei.negative();
                            owedWei = _heldWeiToOwedWei(heldWei, heldPrice, owedPrice);
                
                            state.setPar(
                                args.liquidAccount,
                                args.heldMarket,
                                Types.zeroPar()
                            );
                            state.setParFromDeltaWei(
                                args.liquidAccount,
                                args.owedMarket,
                                owedWei
                            );
                        } else {
                            state.setPar(
                                args.liquidAccount,
                                args.owedMarket,
                                owedPar
                            );
                            state.setParFromDeltaWei(
                                args.liquidAccount,
                                args.heldMarket,
                                heldWei
                            );
                        }
                
                        // set the balances for the solid account
                        state.setParFromDeltaWei(
                            args.solidAccount,
                            args.owedMarket,
                            owedWei.negative()
                        );
                        state.setParFromDeltaWei(
                            args.solidAccount,
                            args.heldMarket,
                            heldWei.negative()
                        );
                
                        Events.logLiquidate(
                            state,
                            args,
                            heldWei,
                            owedWei
                        );
                    }
                
                    function _vaporize(
                        Storage.State storage state,
                        Actions.VaporizeArgs memory args,
                        Cache.MarketCache memory cache
                    )
                        private
                    {
                        state.requireIsOperator(args.solidAccount, msg.sender);
                
                        // verify vaporizable
                        if (Account.Status.Vapor != state.getStatus(args.vaporAccount)) {
                            Require.that(
                                state.isVaporizable(args.vaporAccount, cache),
                                FILE,
                                "Unvaporizable account",
                                args.vaporAccount.owner,
                                args.vaporAccount.number
                            );
                            state.setStatus(args.vaporAccount, Account.Status.Vapor);
                        }
                
                        // First, attempt to refund using the same token
                        (
                            bool fullyRepaid,
                            Types.Wei memory excessWei
                        ) = _vaporizeUsingExcess(state, args);
                        if (fullyRepaid) {
                            Events.logVaporize(
                                state,
                                args,
                                Types.zeroWei(),
                                Types.zeroWei(),
                                excessWei
                            );
                            return;
                        }
                
                        Types.Wei memory maxHeldWei = state.getNumExcessTokens(args.heldMarket);
                
                        Require.that(
                            !maxHeldWei.isNegative(),
                            FILE,
                            "Excess cannot be negative",
                            args.heldMarket
                        );
                
                        (
                            Types.Par memory owedPar,
                            Types.Wei memory owedWei
                        ) = state.getNewParAndDeltaWeiForLiquidation(
                            args.vaporAccount,
                            args.owedMarket,
                            args.amount
                        );
                
                        (
                            Monetary.Price memory heldPrice,
                            Monetary.Price memory owedPrice
                        ) = _getLiquidationPrices(
                            state,
                            cache,
                            args.heldMarket,
                            args.owedMarket
                        );
                
                        Types.Wei memory heldWei = _owedWeiToHeldWei(owedWei, heldPrice, owedPrice);
                
                        // if attempting to over-borrow the held asset, bound it by the maximum
                        if (heldWei.value > maxHeldWei.value) {
                            heldWei = maxHeldWei.negative();
                            owedWei = _heldWeiToOwedWei(heldWei, heldPrice, owedPrice);
                
                            state.setParFromDeltaWei(
                                args.vaporAccount,
                                args.owedMarket,
                                owedWei
                            );
                        } else {
                            state.setPar(
                                args.vaporAccount,
                                args.owedMarket,
                                owedPar
                            );
                        }
                
                        // set the balances for the solid account
                        state.setParFromDeltaWei(
                            args.solidAccount,
                            args.owedMarket,
                            owedWei.negative()
                        );
                        state.setParFromDeltaWei(
                            args.solidAccount,
                            args.heldMarket,
                            heldWei.negative()
                        );
                
                        Events.logVaporize(
                            state,
                            args,
                            heldWei,
                            owedWei,
                            excessWei
                        );
                    }
                
                    function _call(
                        Storage.State storage state,
                        Actions.CallArgs memory args
                    )
                        private
                    {
                        state.requireIsOperator(args.account, msg.sender);
                
                        ICallee(args.callee).callFunction(
                            msg.sender,
                            args.account,
                            args.data
                        );
                
                        Events.logCall(args);
                    }
                
                    // ============ Private Functions ============
                
                    /**
                     * For the purposes of liquidation or vaporization, get the value-equivalent amount of heldWei
                     * given owedWei and the (spread-adjusted) prices of each asset.
                     */
                    function _owedWeiToHeldWei(
                        Types.Wei memory owedWei,
                        Monetary.Price memory heldPrice,
                        Monetary.Price memory owedPrice
                    )
                        private
                        pure
                        returns (Types.Wei memory)
                    {
                        return Types.Wei({
                            sign: false,
                            value: Math.getPartial(owedWei.value, owedPrice.value, heldPrice.value)
                        });
                    }
                
                    /**
                     * For the purposes of liquidation or vaporization, get the value-equivalent amount of owedWei
                     * given heldWei and the (spread-adjusted) prices of each asset.
                     */
                    function _heldWeiToOwedWei(
                        Types.Wei memory heldWei,
                        Monetary.Price memory heldPrice,
                        Monetary.Price memory owedPrice
                    )
                        private
                        pure
                        returns (Types.Wei memory)
                    {
                        return Types.Wei({
                            sign: true,
                            value: Math.getPartialRoundUp(heldWei.value, heldPrice.value, owedPrice.value)
                        });
                    }
                
                    /**
                     * Attempt to vaporize an account's balance using the excess tokens in the protocol. Return a
                     * bool and a wei value. The boolean is true if and only if the balance was fully vaporized. The
                     * Wei value is how many excess tokens were used to partially or fully vaporize the account's
                     * negative balance.
                     */
                    function _vaporizeUsingExcess(
                        Storage.State storage state,
                        Actions.VaporizeArgs memory args
                    )
                        internal
                        returns (bool, Types.Wei memory)
                    {
                        Types.Wei memory excessWei = state.getNumExcessTokens(args.owedMarket);
                
                        // There are no excess funds, return zero
                        if (!excessWei.isPositive()) {
                            return (false, Types.zeroWei());
                        }
                
                        Types.Wei memory maxRefundWei = state.getWei(args.vaporAccount, args.owedMarket);
                        maxRefundWei.sign = true;
                
                        // The account is fully vaporizable using excess funds
                        if (excessWei.value >= maxRefundWei.value) {
                            state.setPar(
                                args.vaporAccount,
                                args.owedMarket,
                                Types.zeroPar()
                            );
                            return (true, maxRefundWei);
                        }
                
                        // The account is only partially vaporizable using excess funds
                        else {
                            state.setParFromDeltaWei(
                                args.vaporAccount,
                                args.owedMarket,
                                excessWei
                            );
                            return (false, excessWei);
                        }
                    }
                
                    /**
                     * Return the (spread-adjusted) prices of two assets for the purposes of liquidation or
                     * vaporization.
                     */
                    function _getLiquidationPrices(
                        Storage.State storage state,
                        Cache.MarketCache memory cache,
                        uint256 heldMarketId,
                        uint256 owedMarketId
                    )
                        internal
                        view
                        returns (
                            Monetary.Price memory,
                            Monetary.Price memory
                        )
                    {
                        uint256 originalPrice = cache.getPrice(owedMarketId).value;
                        Decimal.D256 memory spread = state.getLiquidationSpreadForPair(
                            heldMarketId,
                            owedMarketId
                        );
                
                        Monetary.Price memory owedPrice = Monetary.Price({
                            value: originalPrice.add(Decimal.mul(originalPrice, spread))
                        });
                
                        return (cache.getPrice(heldMarketId), owedPrice);
                    }
                }

                File 4 of 7: DaiPriceOracle
                /*
                
                    Copyright 2019 dYdX Trading Inc.
                
                    Licensed under the Apache License, Version 2.0 (the "License");
                    you may not use this file except in compliance with the License.
                    You may obtain a copy of the License at
                
                    http://www.apache.org/licenses/LICENSE-2.0
                
                    Unless required by applicable law or agreed to in writing, software
                    distributed under the License is distributed on an "AS IS" BASIS,
                    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                    See the License for the specific language governing permissions and
                    limitations under the License.
                
                */
                
                pragma solidity 0.5.7;
                pragma experimental ABIEncoderV2;
                
                // File: openzeppelin-solidity/contracts/math/SafeMath.sol
                
                /**
                 * @title SafeMath
                 * @dev Unsigned math operations with safety checks that revert on error
                 */
                library SafeMath {
                    /**
                    * @dev Multiplies two unsigned integers, reverts on overflow.
                    */
                    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
                        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
                        // benefit is lost if 'b' is also tested.
                        // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
                        if (a == 0) {
                            return 0;
                        }
                
                        uint256 c = a * b;
                        require(c / a == b);
                
                        return c;
                    }
                
                    /**
                    * @dev Integer division of two unsigned integers truncating the quotient, reverts on division by zero.
                    */
                    function div(uint256 a, uint256 b) internal pure returns (uint256) {
                        // Solidity only automatically asserts when dividing by 0
                        require(b > 0);
                        uint256 c = a / b;
                        // assert(a == b * c + a % b); // There is no case in which this doesn't hold
                
                        return c;
                    }
                
                    /**
                    * @dev Subtracts two unsigned integers, reverts on overflow (i.e. if subtrahend is greater than minuend).
                    */
                    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                        require(b <= a);
                        uint256 c = a - b;
                
                        return c;
                    }
                
                    /**
                    * @dev Adds two unsigned integers, reverts on overflow.
                    */
                    function add(uint256 a, uint256 b) internal pure returns (uint256) {
                        uint256 c = a + b;
                        require(c >= a);
                
                        return c;
                    }
                
                    /**
                    * @dev Divides two unsigned integers and returns the remainder (unsigned integer modulo),
                    * reverts when dividing by zero.
                    */
                    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
                        require(b != 0);
                        return a % b;
                    }
                }
                
                // File: openzeppelin-solidity/contracts/ownership/Ownable.sol
                
                /**
                 * @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 private _owner;
                
                    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
                
                    /**
                     * @dev The Ownable constructor sets the original `owner` of the contract to the sender
                     * account.
                     */
                    constructor () internal {
                        _owner = msg.sender;
                        emit OwnershipTransferred(address(0), _owner);
                    }
                
                    /**
                     * @return the address of the owner.
                     */
                    function owner() public view returns (address) {
                        return _owner;
                    }
                
                    /**
                     * @dev Throws if called by any account other than the owner.
                     */
                    modifier onlyOwner() {
                        require(isOwner());
                        _;
                    }
                
                    /**
                     * @return true if `msg.sender` is the owner of the contract.
                     */
                    function isOwner() public view returns (bool) {
                        return msg.sender == _owner;
                    }
                
                    /**
                     * @dev Allows the current owner to relinquish control of the contract.
                     * @notice Renouncing to ownership will leave the contract without an owner.
                     * It will not be possible to call the functions with the `onlyOwner`
                     * modifier anymore.
                     */
                    function renounceOwnership() public onlyOwner {
                        emit OwnershipTransferred(_owner, address(0));
                        _owner = address(0);
                    }
                
                    /**
                     * @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 {
                        _transferOwnership(newOwner);
                    }
                
                    /**
                     * @dev Transfers control of the contract to a newOwner.
                     * @param newOwner The address to transfer ownership to.
                     */
                    function _transferOwnership(address newOwner) internal {
                        require(newOwner != address(0));
                        emit OwnershipTransferred(_owner, newOwner);
                        _owner = newOwner;
                    }
                }
                
                // File: contracts/protocol/interfaces/IErc20.sol
                
                /**
                 * @title IErc20
                 * @author dYdX
                 *
                 * Interface for using ERC20 Tokens. We have to use a special interface to call ERC20 functions so
                 * that we don't automatically revert when calling non-compliant tokens that have no return value for
                 * transfer(), transferFrom(), or approve().
                 */
                interface IErc20 {
                    event Transfer(
                        address indexed from,
                        address indexed to,
                        uint256 value
                    );
                
                    event Approval(
                        address indexed owner,
                        address indexed spender,
                        uint256 value
                    );
                
                    function totalSupply(
                    )
                        external
                        view
                        returns (uint256);
                
                    function balanceOf(
                        address who
                    )
                        external
                        view
                        returns (uint256);
                
                    function allowance(
                        address owner,
                        address spender
                    )
                        external
                        view
                        returns (uint256);
                
                    function transfer(
                        address to,
                        uint256 value
                    )
                        external;
                
                    function transferFrom(
                        address from,
                        address to,
                        uint256 value
                    )
                        external;
                
                    function approve(
                        address spender,
                        uint256 value
                    )
                        external;
                
                    function name()
                        external
                        view
                        returns (string memory);
                
                    function symbol()
                        external
                        view
                        returns (string memory);
                
                    function decimals()
                        external
                        view
                        returns (uint8);
                }
                
                // File: contracts/protocol/lib/Monetary.sol
                
                /**
                 * @title Monetary
                 * @author dYdX
                 *
                 * Library for types involving money
                 */
                library Monetary {
                
                    /*
                     * The price of a base-unit of an asset.
                     */
                    struct Price {
                        uint256 value;
                    }
                
                    /*
                     * Total value of an some amount of an asset. Equal to (price * amount).
                     */
                    struct Value {
                        uint256 value;
                    }
                }
                
                // File: contracts/protocol/interfaces/IPriceOracle.sol
                
                /**
                 * @title IPriceOracle
                 * @author dYdX
                 *
                 * Interface that Price Oracles for Solo must implement in order to report prices.
                 */
                contract IPriceOracle {
                
                    // ============ Constants ============
                
                    uint256 public constant ONE_DOLLAR = 10 ** 36;
                
                    // ============ Public Functions ============
                
                    /**
                     * Get the price of a token
                     *
                     * @param  token  The ERC20 token address of the market
                     * @return        The USD price of a base unit of the token, then multiplied by 10^36.
                     *                So a USD-stable coin with 18 decimal places would return 10^18.
                     *                This is the price of the base unit rather than the price of a "human-readable"
                     *                token amount. Every ERC20 may have a different number of decimals.
                     */
                    function getPrice(
                        address token
                    )
                        public
                        view
                        returns (Monetary.Price memory);
                }
                
                // File: contracts/protocol/lib/Require.sol
                
                /**
                 * @title Require
                 * @author dYdX
                 *
                 * Stringifies parameters to pretty-print revert messages. Costs more gas than regular require()
                 */
                library Require {
                
                    // ============ Constants ============
                
                    uint256 constant ASCII_ZERO = 48; // '0'
                    uint256 constant ASCII_RELATIVE_ZERO = 87; // 'a' - 10
                    uint256 constant ASCII_LOWER_EX = 120; // 'x'
                    bytes2 constant COLON = 0x3a20; // ': '
                    bytes2 constant COMMA = 0x2c20; // ', '
                    bytes2 constant LPAREN = 0x203c; // ' <'
                    byte constant RPAREN = 0x3e; // '>'
                    uint256 constant FOUR_BIT_MASK = 0xf;
                
                    // ============ Library Functions ============
                
                    function that(
                        bool must,
                        bytes32 file,
                        bytes32 reason
                    )
                        internal
                        pure
                    {
                        if (!must) {
                            revert(
                                string(
                                    abi.encodePacked(
                                        stringify(file),
                                        COLON,
                                        stringify(reason)
                                    )
                                )
                            );
                        }
                    }
                
                    function that(
                        bool must,
                        bytes32 file,
                        bytes32 reason,
                        uint256 payloadA
                    )
                        internal
                        pure
                    {
                        if (!must) {
                            revert(
                                string(
                                    abi.encodePacked(
                                        stringify(file),
                                        COLON,
                                        stringify(reason),
                                        LPAREN,
                                        stringify(payloadA),
                                        RPAREN
                                    )
                                )
                            );
                        }
                    }
                
                    function that(
                        bool must,
                        bytes32 file,
                        bytes32 reason,
                        uint256 payloadA,
                        uint256 payloadB
                    )
                        internal
                        pure
                    {
                        if (!must) {
                            revert(
                                string(
                                    abi.encodePacked(
                                        stringify(file),
                                        COLON,
                                        stringify(reason),
                                        LPAREN,
                                        stringify(payloadA),
                                        COMMA,
                                        stringify(payloadB),
                                        RPAREN
                                    )
                                )
                            );
                        }
                    }
                
                    function that(
                        bool must,
                        bytes32 file,
                        bytes32 reason,
                        address payloadA
                    )
                        internal
                        pure
                    {
                        if (!must) {
                            revert(
                                string(
                                    abi.encodePacked(
                                        stringify(file),
                                        COLON,
                                        stringify(reason),
                                        LPAREN,
                                        stringify(payloadA),
                                        RPAREN
                                    )
                                )
                            );
                        }
                    }
                
                    function that(
                        bool must,
                        bytes32 file,
                        bytes32 reason,
                        address payloadA,
                        uint256 payloadB
                    )
                        internal
                        pure
                    {
                        if (!must) {
                            revert(
                                string(
                                    abi.encodePacked(
                                        stringify(file),
                                        COLON,
                                        stringify(reason),
                                        LPAREN,
                                        stringify(payloadA),
                                        COMMA,
                                        stringify(payloadB),
                                        RPAREN
                                    )
                                )
                            );
                        }
                    }
                
                    function that(
                        bool must,
                        bytes32 file,
                        bytes32 reason,
                        address payloadA,
                        uint256 payloadB,
                        uint256 payloadC
                    )
                        internal
                        pure
                    {
                        if (!must) {
                            revert(
                                string(
                                    abi.encodePacked(
                                        stringify(file),
                                        COLON,
                                        stringify(reason),
                                        LPAREN,
                                        stringify(payloadA),
                                        COMMA,
                                        stringify(payloadB),
                                        COMMA,
                                        stringify(payloadC),
                                        RPAREN
                                    )
                                )
                            );
                        }
                    }
                
                    // ============ Private Functions ============
                
                    function stringify(
                        bytes32 input
                    )
                        private
                        pure
                        returns (bytes memory)
                    {
                        // put the input bytes into the result
                        bytes memory result = abi.encodePacked(input);
                
                        // determine the length of the input by finding the location of the last non-zero byte
                        for (uint256 i = 32; i > 0; ) {
                            // reverse-for-loops with unsigned integer
                            /* solium-disable-next-line security/no-modify-for-iter-var */
                            i--;
                
                            // find the last non-zero byte in order to determine the length
                            if (result[i] != 0) {
                                uint256 length = i + 1;
                
                                /* solium-disable-next-line security/no-inline-assembly */
                                assembly {
                                    mstore(result, length) // r.length = length;
                                }
                
                                return result;
                            }
                        }
                
                        // all bytes are zero
                        return new bytes(0);
                    }
                
                    function stringify(
                        uint256 input
                    )
                        private
                        pure
                        returns (bytes memory)
                    {
                        if (input == 0) {
                            return "0";
                        }
                
                        // get the final string length
                        uint256 j = input;
                        uint256 length;
                        while (j != 0) {
                            length++;
                            j /= 10;
                        }
                
                        // allocate the string
                        bytes memory bstr = new bytes(length);
                
                        // populate the string starting with the least-significant character
                        j = input;
                        for (uint256 i = length; i > 0; ) {
                            // reverse-for-loops with unsigned integer
                            /* solium-disable-next-line security/no-modify-for-iter-var */
                            i--;
                
                            // take last decimal digit
                            bstr[i] = byte(uint8(ASCII_ZERO + (j % 10)));
                
                            // remove the last decimal digit
                            j /= 10;
                        }
                
                        return bstr;
                    }
                
                    function stringify(
                        address input
                    )
                        private
                        pure
                        returns (bytes memory)
                    {
                        uint256 z = uint256(input);
                
                        // addresses are "0x" followed by 20 bytes of data which take up 2 characters each
                        bytes memory result = new bytes(42);
                
                        // populate the result with "0x"
                        result[0] = byte(uint8(ASCII_ZERO));
                        result[1] = byte(uint8(ASCII_LOWER_EX));
                
                        // for each byte (starting from the lowest byte), populate the result with two characters
                        for (uint256 i = 0; i < 20; i++) {
                            // each byte takes two characters
                            uint256 shift = i * 2;
                
                            // populate the least-significant character
                            result[41 - shift] = char(z & FOUR_BIT_MASK);
                            z = z >> 4;
                
                            // populate the most-significant character
                            result[40 - shift] = char(z & FOUR_BIT_MASK);
                            z = z >> 4;
                        }
                
                        return result;
                    }
                
                    function char(
                        uint256 input
                    )
                        private
                        pure
                        returns (byte)
                    {
                        // return ASCII digit (0-9)
                        if (input < 10) {
                            return byte(uint8(input + ASCII_ZERO));
                        }
                
                        // return ASCII letter (a-f)
                        return byte(uint8(input + ASCII_RELATIVE_ZERO));
                    }
                }
                
                // File: contracts/protocol/lib/Math.sol
                
                /**
                 * @title Math
                 * @author dYdX
                 *
                 * Library for non-standard Math functions
                 */
                library Math {
                    using SafeMath for uint256;
                
                    // ============ Constants ============
                
                    bytes32 constant FILE = "Math";
                
                    // ============ Library Functions ============
                
                    /*
                     * Return target * (numerator / denominator).
                     */
                    function getPartial(
                        uint256 target,
                        uint256 numerator,
                        uint256 denominator
                    )
                        internal
                        pure
                        returns (uint256)
                    {
                        return target.mul(numerator).div(denominator);
                    }
                
                    /*
                     * Return target * (numerator / denominator), but rounded up.
                     */
                    function getPartialRoundUp(
                        uint256 target,
                        uint256 numerator,
                        uint256 denominator
                    )
                        internal
                        pure
                        returns (uint256)
                    {
                        if (target == 0 || numerator == 0) {
                            // SafeMath will check for zero denominator
                            return SafeMath.div(0, denominator);
                        }
                        return target.mul(numerator).sub(1).div(denominator).add(1);
                    }
                
                    function to128(
                        uint256 number
                    )
                        internal
                        pure
                        returns (uint128)
                    {
                        uint128 result = uint128(number);
                        Require.that(
                            result == number,
                            FILE,
                            "Unsafe cast to uint128"
                        );
                        return result;
                    }
                
                    function to96(
                        uint256 number
                    )
                        internal
                        pure
                        returns (uint96)
                    {
                        uint96 result = uint96(number);
                        Require.that(
                            result == number,
                            FILE,
                            "Unsafe cast to uint96"
                        );
                        return result;
                    }
                
                    function to32(
                        uint256 number
                    )
                        internal
                        pure
                        returns (uint32)
                    {
                        uint32 result = uint32(number);
                        Require.that(
                            result == number,
                            FILE,
                            "Unsafe cast to uint32"
                        );
                        return result;
                    }
                
                    function min(
                        uint256 a,
                        uint256 b
                    )
                        internal
                        pure
                        returns (uint256)
                    {
                        return a < b ? a : b;
                    }
                
                    function max(
                        uint256 a,
                        uint256 b
                    )
                        internal
                        pure
                        returns (uint256)
                    {
                        return a > b ? a : b;
                    }
                }
                
                // File: contracts/protocol/lib/Time.sol
                
                /**
                 * @title Time
                 * @author dYdX
                 *
                 * Library for dealing with time, assuming timestamps fit within 32 bits (valid until year 2106)
                 */
                library Time {
                
                    // ============ Library Functions ============
                
                    function currentTime()
                        internal
                        view
                        returns (uint32)
                    {
                        return Math.to32(block.timestamp);
                    }
                }
                
                // File: contracts/external/interfaces/IMakerOracle.sol
                
                /**
                 * @title IMakerOracle
                 * @author dYdX
                 *
                 * Interface for the price oracles run by MakerDao
                 */
                interface IMakerOracle {
                
                    // Event that is logged when the `note` modifier is used
                    event LogNote(
                        bytes4 indexed msgSig,
                        address indexed msgSender,
                        bytes32 indexed arg1,
                        bytes32 indexed arg2,
                        uint256 msgValue,
                        bytes msgData
                    ) anonymous;
                
                    // returns the current value (ETH/USD * 10**18) as a bytes32
                    function peek()
                        external
                        view
                        returns (bytes32, bool);
                
                    // requires a fresh price and then returns the current value
                    function read()
                        external
                        view
                        returns (bytes32);
                }
                
                // File: contracts/external/interfaces/IOasisDex.sol
                
                /**
                 * @title IOasisDex
                 * @author dYdX
                 *
                 * Interface for the OasisDex contract
                 */
                interface IOasisDex {
                
                    // ============ Structs ================
                
                    struct OfferInfo {
                        uint256 pay_amt;
                        address pay_gem;
                        uint256 buy_amt;
                        address buy_gem;
                        address owner;
                        uint64 timestamp;
                    }
                
                    struct SortInfo {
                        uint256 next;  //points to id of next higher offer
                        uint256 prev;  //points to id of previous lower offer
                        uint256 delb;  //the blocknumber where this entry was marked for delete
                    }
                
                    // ============ Storage Getters ================
                
                    function last_offer_id()
                        external
                        view
                        returns (uint256);
                
                    function offers(
                        uint256 id
                    )
                        external
                        view
                        returns (OfferInfo memory);
                
                    function close_time()
                        external
                        view
                        returns (uint64);
                
                    function stopped()
                        external
                        view
                        returns (bool);
                
                    function buyEnabled()
                        external
                        view
                        returns (bool);
                
                    function matchingEnabled()
                        external
                        view
                        returns (bool);
                
                    function _rank(
                        uint256 id
                    )
                        external
                        view
                        returns (SortInfo memory);
                
                    function _best(
                        address sell_gem,
                        address buy_gem
                    )
                        external
                        view
                        returns (uint256);
                
                    function _span(
                        address sell_gem,
                        address buy_gem
                    )
                        external
                        view
                        returns (uint256);
                
                    function _dust(
                        address gem
                    )
                        external
                        view
                        returns (uint256);
                
                    function _near(
                        uint256 id
                    )
                        external
                        view
                        returns (uint256);
                
                    // ============ Constant Functions ================
                
                    function isActive(
                        uint256 id
                    )
                        external
                        view
                        returns (bool);
                
                    function getOwner(
                        uint256 id
                    )
                        external
                        view
                        returns (address);
                
                    function getOffer(
                        uint256 id
                    )
                        external
                        view
                        returns (uint256, address, uint256, address);
                
                    function getMinSell(
                        address pay_gem
                    )
                        external
                        view
                        returns (uint256);
                
                    function getBestOffer(
                        address sell_gem,
                        address buy_gem
                    )
                        external
                        view
                        returns (uint256);
                
                    function getWorseOffer(
                        uint256 id
                    )
                        external
                        view
                        returns (uint256);
                
                    function getBetterOffer(
                        uint256 id
                    )
                        external
                        view
                        returns (uint256);
                
                    function getOfferCount(
                        address sell_gem,
                        address buy_gem
                    )
                        external
                        view
                        returns (uint256);
                
                    function getFirstUnsortedOffer()
                        external
                        view
                        returns (uint256);
                
                    function getNextUnsortedOffer(
                        uint256 id
                    )
                        external
                        view
                        returns (uint256);
                
                    function isOfferSorted(
                        uint256 id
                    )
                        external
                        view
                        returns (bool);
                
                    function getBuyAmount(
                        address buy_gem,
                        address pay_gem,
                        uint256 pay_amt
                    )
                        external
                        view
                        returns (uint256);
                
                    function getPayAmount(
                        address pay_gem,
                        address buy_gem,
                        uint256 buy_amt
                    )
                        external
                        view
                        returns (uint256);
                
                    function isClosed()
                        external
                        view
                        returns (bool);
                
                    function getTime()
                        external
                        view
                        returns (uint64);
                
                    // ============ Non-Constant Functions ================
                
                    function bump(
                        bytes32 id_
                    )
                        external;
                
                    function buy(
                        uint256 id,
                        uint256 quantity
                    )
                        external
                        returns (bool);
                
                    function cancel(
                        uint256 id
                    )
                        external
                        returns (bool);
                
                    function kill(
                        bytes32 id
                    )
                        external;
                
                    function make(
                        address  pay_gem,
                        address  buy_gem,
                        uint128  pay_amt,
                        uint128  buy_amt
                    )
                        external
                        returns (bytes32);
                
                    function take(
                        bytes32 id,
                        uint128 maxTakeAmount
                    )
                        external;
                
                    function offer(
                        uint256 pay_amt,
                        address pay_gem,
                        uint256 buy_amt,
                        address buy_gem
                    )
                        external
                        returns (uint256);
                
                    function offer(
                        uint256 pay_amt,
                        address pay_gem,
                        uint256 buy_amt,
                        address buy_gem,
                        uint256 pos
                    )
                        external
                        returns (uint256);
                
                    function offer(
                        uint256 pay_amt,
                        address pay_gem,
                        uint256 buy_amt,
                        address buy_gem,
                        uint256 pos,
                        bool rounding
                    )
                        external
                        returns (uint256);
                
                    function insert(
                        uint256 id,
                        uint256 pos
                    )
                        external
                        returns (bool);
                
                    function del_rank(
                        uint256 id
                    )
                        external
                        returns (bool);
                
                    function sellAllAmount(
                        address pay_gem,
                        uint256 pay_amt,
                        address buy_gem,
                        uint256 min_fill_amount
                    )
                        external
                        returns (uint256);
                
                    function buyAllAmount(
                        address buy_gem,
                        uint256 buy_amt,
                        address pay_gem,
                        uint256 max_fill_amount
                    )
                        external
                        returns (uint256);
                }
                
                // File: contracts/external/oracles/DaiPriceOracle.sol
                
                /**
                 * @title DaiPriceOracle
                 * @author dYdX
                 *
                 * PriceOracle that gives the price of Dai in USD
                 */
                contract DaiPriceOracle is
                    Ownable,
                    IPriceOracle
                {
                    using SafeMath for uint256;
                
                    // ============ Constants ============
                
                    bytes32 constant FILE = "DaiPriceOracle";
                
                    uint256 constant DECIMALS = 18;
                
                    uint256 constant EXPECTED_PRICE = ONE_DOLLAR / (10 ** DECIMALS);
                
                    // ============ Structs ============
                
                    struct PriceInfo {
                        uint128 price;
                        uint32 lastUpdate;
                    }
                
                    struct DeviationParams {
                        uint64 denominator;
                        uint64 maximumPerSecond;
                        uint64 maximumAbsolute;
                    }
                
                    // ============ Events ============
                
                    event PriceSet(
                        PriceInfo newPriceInfo
                    );
                
                    // ============ Storage ============
                
                    PriceInfo public g_priceInfo;
                
                    address public g_poker;
                
                    DeviationParams public DEVIATION_PARAMS;
                
                    uint256 public OASIS_ETH_AMOUNT;
                
                    IErc20 public WETH;
                
                    IErc20 public DAI;
                
                    IMakerOracle public MEDIANIZER;
                
                    IOasisDex public OASIS;
                
                    address public UNISWAP;
                
                    // ============ Constructor =============
                
                    constructor(
                        address poker,
                        address weth,
                        address dai,
                        address medianizer,
                        address oasis,
                        address uniswap,
                        uint256 oasisEthAmount,
                        DeviationParams memory deviationParams
                    )
                        public
                    {
                        g_poker = poker;
                        MEDIANIZER = IMakerOracle(medianizer);
                        WETH = IErc20(weth);
                        DAI = IErc20(dai);
                        OASIS = IOasisDex(oasis);
                        UNISWAP = uniswap;
                        DEVIATION_PARAMS = deviationParams;
                        OASIS_ETH_AMOUNT = oasisEthAmount;
                        g_priceInfo = PriceInfo({
                            lastUpdate: uint32(block.timestamp),
                            price: uint128(EXPECTED_PRICE)
                        });
                    }
                
                    // ============ Admin Functions ============
                
                    function ownerSetPokerAddress(
                        address newPoker
                    )
                        external
                        onlyOwner
                    {
                        g_poker = newPoker;
                    }
                
                    // ============ Public Functions ============
                
                    function updatePrice(
                        Monetary.Price memory minimum,
                        Monetary.Price memory maximum
                    )
                        public
                        returns (PriceInfo memory)
                    {
                        Require.that(
                            msg.sender == g_poker,
                            FILE,
                            "Only poker can call updatePrice",
                            msg.sender
                        );
                
                        Monetary.Price memory newPrice = getBoundedTargetPrice();
                
                        Require.that(
                            newPrice.value >= minimum.value,
                            FILE,
                            "newPrice below minimum",
                            newPrice.value,
                            minimum.value
                        );
                
                        Require.that(
                            newPrice.value <= maximum.value,
                            FILE,
                            "newPrice above maximum",
                            newPrice.value,
                            maximum.value
                        );
                
                        g_priceInfo = PriceInfo({
                            price: Math.to128(newPrice.value),
                            lastUpdate: Time.currentTime()
                        });
                
                        emit PriceSet(g_priceInfo);
                        return g_priceInfo;
                    }
                
                    // ============ IPriceOracle Functions ============
                
                    function getPrice(
                        address /* token */
                    )
                        public
                        view
                        returns (Monetary.Price memory)
                    {
                        return Monetary.Price({
                            value: g_priceInfo.price
                        });
                    }
                
                    // ============ Price-Query Functions ============
                
                    /**
                     * Get the new price that would be stored if updated right now.
                     */
                    function getBoundedTargetPrice()
                        public
                        view
                        returns (Monetary.Price memory)
                    {
                        Monetary.Price memory targetPrice = getTargetPrice();
                
                        PriceInfo memory oldInfo = g_priceInfo;
                        uint256 timeDelta = uint256(Time.currentTime()).sub(oldInfo.lastUpdate);
                        (uint256 minPrice, uint256 maxPrice) = getPriceBounds(oldInfo.price, timeDelta);
                        uint256 boundedTargetPrice = boundValue(targetPrice.value, minPrice, maxPrice);
                
                        return Monetary.Price({
                            value: boundedTargetPrice
                        });
                    }
                
                    /**
                     * Get the USD price of DAI that this contract will move towards when updated. This price is
                     * not bounded by the variables governing the maximum deviation from the old price.
                     */
                    function getTargetPrice()
                        public
                        view
                        returns (Monetary.Price memory)
                    {
                        Monetary.Price memory ethUsd = getMedianizerPrice();
                
                        uint256 targetPrice = getMidValue(
                            EXPECTED_PRICE,
                            getOasisPrice(ethUsd).value,
                            getUniswapPrice(ethUsd).value
                        );
                
                        return Monetary.Price({
                            value: targetPrice
                        });
                    }
                
                    /**
                     * Get the USD price of ETH according the Maker Medianizer contract.
                     */
                    function getMedianizerPrice()
                        public
                        view
                        returns (Monetary.Price memory)
                    {
                        // throws if the price is not fresh
                        return Monetary.Price({
                            value: uint256(MEDIANIZER.read())
                        });
                    }
                
                    /**
                     * Get the USD price of DAI according to OasisDEX given the USD price of ETH.
                     */
                    function getOasisPrice(
                        Monetary.Price memory ethUsd
                    )
                        public
                        view
                        returns (Monetary.Price memory)
                    {
                        IOasisDex oasis = OASIS;
                
                        // If exchange is not operational, return old value.
                        // This allows the price to move only towards 1 USD
                        if (
                            oasis.isClosed()
                            || !oasis.buyEnabled()
                            || !oasis.matchingEnabled()
                        ) {
                            return Monetary.Price({
                                value: g_priceInfo.price
                            });
                        }
                
                        uint256 numWei = OASIS_ETH_AMOUNT;
                        address dai = address(DAI);
                        address weth = address(WETH);
                
                        // Assumes at least `numWei` of depth on both sides of the book if the exchange is active.
                        // Will revert if not enough depth.
                        uint256 daiAmt1 = oasis.getBuyAmount(dai, weth, numWei);
                        uint256 daiAmt2 = oasis.getPayAmount(dai, weth, numWei);
                
                        uint256 num = numWei.mul(daiAmt2).add(numWei.mul(daiAmt1));
                        uint256 den = daiAmt1.mul(daiAmt2).mul(2);
                        uint256 oasisPrice = Math.getPartial(ethUsd.value, num, den);
                
                        return Monetary.Price({
                            value: oasisPrice
                        });
                    }
                
                    /**
                     * Get the USD price of DAI according to Uniswap given the USD price of ETH.
                     */
                    function getUniswapPrice(
                        Monetary.Price memory ethUsd
                    )
                        public
                        view
                        returns (Monetary.Price memory)
                    {
                        address uniswap = address(UNISWAP);
                        uint256 ethAmt = uniswap.balance;
                        uint256 daiAmt = DAI.balanceOf(uniswap);
                        uint256 uniswapPrice = Math.getPartial(ethUsd.value, ethAmt, daiAmt);
                
                        return Monetary.Price({
                            value: uniswapPrice
                        });
                    }
                
                    // ============ Helper Functions ============
                
                    function getPriceBounds(
                        uint256 oldPrice,
                        uint256 timeDelta
                    )
                        private
                        view
                        returns (uint256, uint256)
                    {
                        DeviationParams memory deviation = DEVIATION_PARAMS;
                
                        uint256 maxDeviation = Math.getPartial(
                            oldPrice,
                            Math.min(deviation.maximumAbsolute, timeDelta.mul(deviation.maximumPerSecond)),
                            deviation.denominator
                        );
                
                        return (
                            oldPrice.sub(maxDeviation),
                            oldPrice.add(maxDeviation)
                        );
                    }
                
                    function getMidValue(
                        uint256 valueA,
                        uint256 valueB,
                        uint256 valueC
                    )
                        private
                        pure
                        returns (uint256)
                    {
                        uint256 maximum = Math.max(valueA, Math.max(valueB, valueC));
                        if (maximum == valueA) {
                            return Math.max(valueB, valueC);
                        }
                        if (maximum == valueB) {
                            return Math.max(valueA, valueC);
                        }
                        return Math.max(valueA, valueB);
                    }
                
                    function boundValue(
                        uint256 value,
                        uint256 minimum,
                        uint256 maximum
                    )
                        private
                        pure
                        returns (uint256)
                    {
                        assert(minimum <= maximum);
                        return Math.max(minimum, Math.min(maximum, value));
                    }
                }

                File 5 of 7: UsdcPriceOracle
                /*
                
                    Copyright 2019 dYdX Trading Inc.
                
                    Licensed under the Apache License, Version 2.0 (the "License");
                    you may not use this file except in compliance with the License.
                    You may obtain a copy of the License at
                
                    http://www.apache.org/licenses/LICENSE-2.0
                
                    Unless required by applicable law or agreed to in writing, software
                    distributed under the License is distributed on an "AS IS" BASIS,
                    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                    See the License for the specific language governing permissions and
                    limitations under the License.
                
                */
                
                pragma solidity 0.5.7;
                pragma experimental ABIEncoderV2;
                
                // File: contracts/protocol/lib/Monetary.sol
                
                /**
                 * @title Monetary
                 * @author dYdX
                 *
                 * Library for types involving money
                 */
                library Monetary {
                
                    /*
                     * The price of a base-unit of an asset.
                     */
                    struct Price {
                        uint256 value;
                    }
                
                    /*
                     * Total value of an some amount of an asset. Equal to (price * amount).
                     */
                    struct Value {
                        uint256 value;
                    }
                }
                
                // File: contracts/protocol/interfaces/IPriceOracle.sol
                
                /**
                 * @title IPriceOracle
                 * @author dYdX
                 *
                 * Interface that Price Oracles for Solo must implement in order to report prices.
                 */
                contract IPriceOracle {
                
                    // ============ Constants ============
                
                    uint256 public constant ONE_DOLLAR = 10 ** 36;
                
                    // ============ Public Functions ============
                
                    /**
                     * Get the price of a token
                     *
                     * @param  token  The ERC20 token address of the market
                     * @return        The USD price of a base unit of the token, then multiplied by 10^36.
                     *                So a USD-stable coin with 18 decimal places would return 10^18.
                     *                This is the price of the base unit rather than the price of a "human-readable"
                     *                token amount. Every ERC20 may have a different number of decimals.
                     */
                    function getPrice(
                        address token
                    )
                        public
                        view
                        returns (Monetary.Price memory);
                }
                
                // File: contracts/external/oracles/UsdcPriceOracle.sol
                
                /**
                 * @title UsdcPriceOracle
                 * @author dYdX
                 *
                 * PriceOracle that returns the price of USDC in USD
                 */
                contract UsdcPriceOracle is
                    IPriceOracle
                {
                    // ============ Constants ============
                
                    uint256 constant DECIMALS = 6;
                
                    uint256 constant EXPECTED_PRICE = ONE_DOLLAR / (10 ** DECIMALS);
                
                    // ============ IPriceOracle Functions =============
                
                    function getPrice(
                        address /* token */
                    )
                        public
                        view
                        returns (Monetary.Price memory)
                    {
                        return Monetary.Price({ value: EXPECTED_PRICE });
                    }
                }

                File 6 of 7: WethPriceOracle
                /*
                
                    Copyright 2019 dYdX Trading Inc.
                
                    Licensed under the Apache License, Version 2.0 (the "License");
                    you may not use this file except in compliance with the License.
                    You may obtain a copy of the License at
                
                    http://www.apache.org/licenses/LICENSE-2.0
                
                    Unless required by applicable law or agreed to in writing, software
                    distributed under the License is distributed on an "AS IS" BASIS,
                    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                    See the License for the specific language governing permissions and
                    limitations under the License.
                
                */
                
                pragma solidity 0.5.7;
                pragma experimental ABIEncoderV2;
                
                // File: contracts/protocol/lib/Monetary.sol
                
                /**
                 * @title Monetary
                 * @author dYdX
                 *
                 * Library for types involving money
                 */
                library Monetary {
                
                    /*
                     * The price of a base-unit of an asset.
                     */
                    struct Price {
                        uint256 value;
                    }
                
                    /*
                     * Total value of an some amount of an asset. Equal to (price * amount).
                     */
                    struct Value {
                        uint256 value;
                    }
                }
                
                // File: contracts/protocol/interfaces/IPriceOracle.sol
                
                /**
                 * @title IPriceOracle
                 * @author dYdX
                 *
                 * Interface that Price Oracles for Solo must implement in order to report prices.
                 */
                contract IPriceOracle {
                
                    // ============ Constants ============
                
                    uint256 public constant ONE_DOLLAR = 10 ** 36;
                
                    // ============ Public Functions ============
                
                    /**
                     * Get the price of a token
                     *
                     * @param  token  The ERC20 token address of the market
                     * @return        The USD price of a base unit of the token, then multiplied by 10^36.
                     *                So a USD-stable coin with 18 decimal places would return 10^18.
                     *                This is the price of the base unit rather than the price of a "human-readable"
                     *                token amount. Every ERC20 may have a different number of decimals.
                     */
                    function getPrice(
                        address token
                    )
                        public
                        view
                        returns (Monetary.Price memory);
                }
                
                // File: contracts/external/interfaces/IMakerOracle.sol
                
                /**
                 * @title IMakerOracle
                 * @author dYdX
                 *
                 * Interface for the price oracles run by MakerDao
                 */
                interface IMakerOracle {
                
                    // Event that is logged when the `note` modifier is used
                    event LogNote(
                        bytes4 indexed msgSig,
                        address indexed msgSender,
                        bytes32 indexed arg1,
                        bytes32 indexed arg2,
                        uint256 msgValue,
                        bytes msgData
                    ) anonymous;
                
                    // returns the current value (ETH/USD * 10**18) as a bytes32
                    function peek()
                        external
                        view
                        returns (bytes32, bool);
                
                    // requires a fresh price and then returns the current value
                    function read()
                        external
                        view
                        returns (bytes32);
                }
                
                // File: contracts/external/oracles/WethPriceOracle.sol
                
                /**
                 * @title WethPriceOracle
                 * @author dYdX
                 *
                 * PriceOracle that returns the price of Wei in USD
                 */
                contract WethPriceOracle is
                    IPriceOracle
                {
                    // ============ Storage ============
                
                    IMakerOracle public MEDIANIZER;
                
                    // ============ Constructor =============
                
                    constructor(
                        address medianizer
                    )
                        public
                    {
                        MEDIANIZER = IMakerOracle(medianizer);
                    }
                
                    // ============ IPriceOracle Functions =============
                
                    function getPrice(
                        address /* token */
                    )
                        public
                        view
                        returns (Monetary.Price memory)
                    {
                        (bytes32 value, /* bool fresh */) = MEDIANIZER.peek();
                        return Monetary.Price({ value: uint256(value) });
                    }
                }

                File 7 of 7: Medianizer
                /// return median value of feeds
                
                // Copyright (C) 2017  DappHub, LLC
                
                // Licensed under the Apache License, Version 2.0 (the "License").
                // You may not use this file except in compliance with the License.
                
                // Unless required by applicable law or agreed to in writing, software
                // distributed under the License is distributed on an "AS IS" BASIS,
                // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND (express or implied).
                
                pragma solidity ^0.4.8;
                
                contract DSAuthority {
                    function canCall(
                        address src, address dst, bytes4 sig
                    ) constant returns (bool);
                }
                
                contract DSAuthEvents {
                    event LogSetAuthority (address indexed authority);
                    event LogSetOwner     (address indexed owner);
                }
                
                contract DSAuth is DSAuthEvents {
                    DSAuthority  public  authority;
                    address      public  owner;
                
                    function DSAuth() {
                        owner = msg.sender;
                        LogSetOwner(msg.sender);
                    }
                
                    function setOwner(address owner_)
                        auth
                    {
                        owner = owner_;
                        LogSetOwner(owner);
                    }
                
                    function setAuthority(DSAuthority authority_)
                        auth
                    {
                        authority = authority_;
                        LogSetAuthority(authority);
                    }
                
                    modifier auth {
                        assert(isAuthorized(msg.sender, msg.sig));
                        _;
                    }
                
                    modifier authorized(bytes4 sig) {
                        assert(isAuthorized(msg.sender, sig));
                        _;
                    }
                
                    function isAuthorized(address src, bytes4 sig) internal 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);
                        }
                    }
                
                    function assert(bool x) internal {
                        if (!x) throw;
                    }
                }
                
                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)
                        }
                
                        LogNote(msg.sig, msg.sender, foo, bar, msg.value, msg.data);
                
                        _;
                    }
                }
                
                contract DSMath {
                    
                    /*
                    standard uint256 functions
                     */
                
                    function add(uint256 x, uint256 y) constant internal returns (uint256 z) {
                        assert((z = x + y) >= x);
                    }
                
                    function sub(uint256 x, uint256 y) constant internal returns (uint256 z) {
                        assert((z = x - y) <= x);
                    }
                
                    function mul(uint256 x, uint256 y) constant internal returns (uint256 z) {
                        assert((z = x * y) >= x);
                    }
                
                    function div(uint256 x, uint256 y) constant internal returns (uint256 z) {
                        z = x / y;
                    }
                
                    function min(uint256 x, uint256 y) constant internal returns (uint256 z) {
                        return x <= y ? x : y;
                    }
                    function max(uint256 x, uint256 y) constant internal returns (uint256 z) {
                        return x >= y ? x : y;
                    }
                
                    /*
                    uint128 functions (h is for half)
                     */
                
                
                    function hadd(uint128 x, uint128 y) constant internal returns (uint128 z) {
                        assert((z = x + y) >= x);
                    }
                
                    function hsub(uint128 x, uint128 y) constant internal returns (uint128 z) {
                        assert((z = x - y) <= x);
                    }
                
                    function hmul(uint128 x, uint128 y) constant internal returns (uint128 z) {
                        assert((z = x * y) >= x);
                    }
                
                    function hdiv(uint128 x, uint128 y) constant internal returns (uint128 z) {
                        z = x / y;
                    }
                
                    function hmin(uint128 x, uint128 y) constant internal returns (uint128 z) {
                        return x <= y ? x : y;
                    }
                    function hmax(uint128 x, uint128 y) constant internal returns (uint128 z) {
                        return x >= y ? x : y;
                    }
                
                
                    /*
                    int256 functions
                     */
                
                    function imin(int256 x, int256 y) constant internal returns (int256 z) {
                        return x <= y ? x : y;
                    }
                    function imax(int256 x, int256 y) constant internal returns (int256 z) {
                        return x >= y ? x : y;
                    }
                
                    /*
                    WAD math
                     */
                
                    uint128 constant WAD = 10 ** 18;
                
                    function wadd(uint128 x, uint128 y) constant internal returns (uint128) {
                        return hadd(x, y);
                    }
                
                    function wsub(uint128 x, uint128 y) constant internal returns (uint128) {
                        return hsub(x, y);
                    }
                
                    function wmul(uint128 x, uint128 y) constant internal returns (uint128 z) {
                        z = cast((uint256(x) * y + WAD / 2) / WAD);
                    }
                
                    function wdiv(uint128 x, uint128 y) constant internal returns (uint128 z) {
                        z = cast((uint256(x) * WAD + y / 2) / y);
                    }
                
                    function wmin(uint128 x, uint128 y) constant internal returns (uint128) {
                        return hmin(x, y);
                    }
                    function wmax(uint128 x, uint128 y) constant internal returns (uint128) {
                        return hmax(x, y);
                    }
                
                    /*
                    RAY math
                     */
                
                    uint128 constant RAY = 10 ** 27;
                
                    function radd(uint128 x, uint128 y) constant internal returns (uint128) {
                        return hadd(x, y);
                    }
                
                    function rsub(uint128 x, uint128 y) constant internal returns (uint128) {
                        return hsub(x, y);
                    }
                
                    function rmul(uint128 x, uint128 y) constant internal returns (uint128 z) {
                        z = cast((uint256(x) * y + RAY / 2) / RAY);
                    }
                
                    function rdiv(uint128 x, uint128 y) constant internal returns (uint128 z) {
                        z = cast((uint256(x) * RAY + y / 2) / y);
                    }
                
                    function rpow(uint128 x, uint64 n) constant internal returns (uint128 z) {
                        // 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].
                
                        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);
                            }
                        }
                    }
                
                    function rmin(uint128 x, uint128 y) constant internal returns (uint128) {
                        return hmin(x, y);
                    }
                    function rmax(uint128 x, uint128 y) constant internal returns (uint128) {
                        return hmax(x, y);
                    }
                
                    function cast(uint256 x) constant internal returns (uint128 z) {
                        assert((z = uint128(x)) == x);
                    }
                
                }
                
                contract DSThing is DSAuth, DSNote, DSMath {
                }
                
                contract DSValue is DSThing {
                    bool    has;
                    bytes32 val;
                    function peek() constant returns (bytes32, bool) {
                        return (val,has);
                    }
                    function read() constant returns (bytes32) {
                        var (wut, has) = peek();
                        assert(has);
                        return wut;
                    }
                    function poke(bytes32 wut) note auth {
                        val = wut;
                        has = true;
                    }
                    function void() note auth { // unset the value
                        has = false;
                    }
                }
                
                contract Medianizer is DSValue {
                    mapping (bytes12 => address) public values;
                    mapping (address => bytes12) public indexes;
                    bytes12 public next = 0x1;
                
                    uint96 public min = 0x1;
                
                    function set(address wat) auth {
                        bytes12 nextId = bytes12(uint96(next) + 1);
                        assert(nextId != 0x0);
                        set(next, wat);
                        next = nextId;
                    }
                
                    function set(bytes12 pos, address wat) note auth {
                        if (pos == 0x0) throw;
                
                        if (wat != 0 && indexes[wat] != 0) throw;
                
                        indexes[values[pos]] = 0; // Making sure to remove a possible existing address in that position
                
                        if (wat != 0) {
                            indexes[wat] = pos;
                        }
                
                        values[pos] = wat;
                    }
                
                    function setMin(uint96 min_) note auth {
                        if (min_ == 0x0) throw;
                        min = min_;
                    }
                
                    function setNext(bytes12 next_) note auth {
                        if (next_ == 0x0) throw;
                        next = next_;
                    }
                
                    function unset(bytes12 pos) {
                        set(pos, 0);
                    }
                
                    function unset(address wat) {
                        set(indexes[wat], 0);
                    }
                
                    function poke() {
                        poke(0);
                    }
                
                    function poke(bytes32) note {
                        (val, has) = compute();
                    }
                
                    function compute() constant returns (bytes32, bool) {
                        bytes32[] memory wuts = new bytes32[](uint96(next) - 1);
                        uint96 ctr = 0;
                        for (uint96 i = 1; i < uint96(next); i++) {
                            if (values[bytes12(i)] != 0) {
                                var (wut, wuz) = DSValue(values[bytes12(i)]).peek();
                                if (wuz) {
                                    if (ctr == 0 || wut >= wuts[ctr - 1]) {
                                        wuts[ctr] = wut;
                                    } else {
                                        uint96 j = 0;
                                        while (wut >= wuts[j]) {
                                            j++;
                                        }
                                        for (uint96 k = ctr; k > j; k--) {
                                            wuts[k] = wuts[k - 1];
                                        }
                                        wuts[j] = wut;
                                    }
                                    ctr++;
                                }
                            }
                        }
                
                        if (ctr < min) return (val, false);
                
                        bytes32 value;
                        if (ctr % 2 == 0) {
                            uint128 val1 = uint128(wuts[(ctr / 2) - 1]);
                            uint128 val2 = uint128(wuts[ctr / 2]);
                            value = bytes32(wdiv(hadd(val1, val2), 2 ether));
                        } else {
                            value = wuts[(ctr - 1) / 2];
                        }
                
                        return (value, true);
                    }
                
                }