ETH Price: $2,297.10 (-0.93%)

Contract

0xF05a3E4e864689eB20f0e27454b76da02589F638
 

Overview

ETH Balance

0.033855025 ETH

Eth Value

$77.77 (@ $2,297.10/ETH)

Token Holdings

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
Withdraw Cntr93243792020-01-21 10:41:521702 days ago1579603312IN
0xF05a3E4e...02589F638
0 ETH0.000023051
Withdraw Cntr93243562020-01-21 10:36:261702 days ago1579602986IN
0xF05a3E4e...02589F638
0 ETH0.000022421
Create Order93243322020-01-21 10:30:211702 days ago1579602621IN
0xF05a3E4e...02589F638
0 ETH0.000300661
Transfer From Ba...93242852020-01-21 10:21:521702 days ago1579602112IN
0xF05a3E4e...02589F638
0 ETH0.000047131
Withdraw Cntr59994592018-07-20 18:18:372251 days ago1532110717IN
0xF05a3E4e...02589F638
0 ETH0.00019028.5
Deposit Cntr59705672018-07-15 19:42:102256 days ago1531683730IN
0xF05a3E4e...02589F638
0.011 ETH0.00043159.8
Withdraw Cntr58891912018-07-01 23:25:472270 days ago1530487547IN
0xF05a3E4e...02589F638
0 ETH0.0006713130
Cancel Order58891852018-07-01 23:22:412270 days ago1530487361IN
0xF05a3E4e...02589F638
0 ETH0.0013378230
Withdraw Cntr58891762018-07-01 23:20:022270 days ago1530487202IN
0xF05a3E4e...02589F638
0 ETH0.0006927531
Create Order58888502018-07-01 21:58:522270 days ago1530482332IN
0xF05a3E4e...02589F638
0 ETH0.0081650233
Deposit Cntr58887212018-07-01 21:28:302270 days ago1530480510IN
0xF05a3E4e...02589F638
0.03 ETH0.0013063945
Deposit Cntr58886712018-07-01 21:14:382270 days ago1530479678IN
0xF05a3E4e...02589F638
0.08 ETH0.0015851136
Withdraw Cntr55754772018-05-08 2:34:112325 days ago1525746851IN
0xF05a3E4e...02589F638
0 ETH0.000022441
Cancel Order55754642018-05-08 2:29:342325 days ago1525746574IN
0xF05a3E4e...02589F638
0 ETH0.000049891
Withdraw Cntr55753142018-05-08 1:52:142325 days ago1525744334IN
0xF05a3E4e...02589F638
0 ETH0.000022371
Create Order55753012018-05-08 1:48:442325 days ago1525744124IN
0xF05a3E4e...02589F638
0 ETH0.000248091
Cancel Order55752942018-05-08 1:46:552325 days ago1525744015IN
0xF05a3E4e...02589F638
0 ETH0.000034831
Create Order55752832018-05-08 1:44:362325 days ago1525743876IN
0xF05a3E4e...02589F638
0 ETH0.000248031
Deposit Cntr55752672018-05-08 1:40:582325 days ago1525743658IN
0xF05a3E4e...02589F638
0.107 ETH0.000044031
Withdraw Cntr50601232018-02-09 17:05:492412 days ago1518195949IN
0xF05a3E4e...02589F638
0 ETH0.0004475420
Cancel Order50600962018-02-09 17:00:422412 days ago1518195642IN
0xF05a3E4e...02589F638
0 ETH0.0008918820
Create Order50007052018-01-30 16:43:122422 days ago1517330592IN
0xF05a3E4e...02589F638
0 ETH0.004948520
Deposit Cntr50006672018-01-30 16:32:412422 days ago1517329961IN
0xF05a3E4e...02589F638
0.00804 ETH0.0005806220
Deposit Cntr50006402018-01-30 16:24:552422 days ago1517329495IN
0xF05a3E4e...02589F638
0.00201 ETH0.0018052741
Withdraw Cntr49943732018-01-29 14:47:172423 days ago1517237237IN
0xF05a3E4e...02589F638
0 ETH0.0004699121
View all transactions

Latest 8 internal transactions

Advanced mode:
Parent Transaction Hash Block From To
93243792020-01-21 10:41:521702 days ago1579603312
0xF05a3E4e...02589F638
0.01004497 ETH
59994592018-07-20 18:18:372251 days ago1532110717
0xF05a3E4e...02589F638
0.011 ETH
58891912018-07-01 23:25:472270 days ago1530487547
0xF05a3E4e...02589F638
0.11 ETH
55754772018-05-08 2:34:112325 days ago1525746851
0xF05a3E4e...02589F638
0.106533 ETH
55753142018-05-08 1:52:142325 days ago1525744334
0xF05a3E4e...02589F638
0.000467 ETH
50601232018-02-09 17:05:492412 days ago1518195949
0xF05a3E4e...02589F638
0.01005 ETH
49943732018-01-29 14:47:172423 days ago1517237237
0xF05a3E4e...02589F638
0.015 ETH
46030932017-11-22 21:46:322491 days ago1511387192  Contract Creation0 ETH
Loading...
Loading

Similar Match Source Code
This contract matches the deployed Bytecode of the Source Code for Contract 0xc36b7ce1...371921a69
The constructor portion of the code might be different and could alter the actual behaviour of the contract

Contract Name:
BookERC20EthV1p1

Compiler Version
v0.4.11+commit.68ef5810

Optimization Enabled:
Yes with 200 runs

Other Settings:
default evmVersion

Contract Source Code (Solidity)

/**
 *Submitted for verification at Etherscan.io on 2017-11-26
*/

pragma solidity ^0.4.11;

// NB: this is the newer ERC20 returning bool, need different book contract for older style tokens
contract ERC20 {
  function totalSupply() constant returns (uint);
  function balanceOf(address _owner) constant returns (uint balance);
  function transfer(address _to, uint _value) returns (bool success);
  function transferFrom(address _from, address _to, uint _value) returns (bool success);
  function approve(address _spender, uint _value) returns (bool success);
  function allowance(address _owner, address _spender) constant returns (uint remaining);
  event Transfer(address indexed _from, address indexed _to, uint _value);
  event Approval(address indexed _owner, address indexed _spender, uint _value);
}

// UbiTok.io on-chain continuous limit order book matching engine.
// This variation is for a "nice" ERC20 token as base, ETH as quoted, and standard fees with reward token.
// Copyright (c) Bonnag Limited. All Rights Reserved.
// Version 1.1.0.
// This contract allows minPriceExponent, baseMinInitialSize, and baseMinRemainingSize
// to be set at init() time appropriately for the token decimals and likely value.
//
contract BookERC20EthV1p1 {

  enum BookType {
    ERC20EthV1
  }

  enum Direction {
    Invalid,
    Buy,
    Sell
  }

  enum Status {
    Unknown,
    Rejected,
    Open,
    Done,
    NeedsGas,
    Sending, // not used by contract - web only
    FailedSend, // not used by contract - web only
    FailedTxn // not used by contract - web only
  }

  enum ReasonCode {
    None,
    InvalidPrice,
    InvalidSize,
    InvalidTerms,
    InsufficientFunds,
    WouldTake,
    Unmatched,
    TooManyMatches,
    ClientCancel
  }

  enum Terms {
    GTCNoGasTopup,
    GTCWithGasTopup,
    ImmediateOrCancel,
    MakerOnly
  }

  struct Order {
    // these are immutable once placed:

    address client;
    uint16 price;              // packed representation of side + price
    uint sizeBase;
    Terms terms;

    // these are mutable until Done or Rejected:
    
    Status status;
    ReasonCode reasonCode;
    uint128 executedBase;      // gross amount executed in base currency (before fee deduction)
    uint128 executedCntr;      // gross amount executed in counter currency (before fee deduction)
    uint128 feesBaseOrCntr;    // base for buy, cntr for sell
    uint128 feesRwrd;
  }
  
  struct OrderChain {
    uint128 firstOrderId;
    uint128 lastOrderId;
  }

  struct OrderChainNode {
    uint128 nextOrderId;
    uint128 prevOrderId;
  }
  
  // It should be possible to reconstruct the expected state of the contract given:
  //  - ClientPaymentEvent log history
  //  - ClientOrderEvent log history
  //  - Calling getOrder for the other immutable order fields of orders referenced by ClientOrderEvent
  
  enum ClientPaymentEventType {
    Deposit,
    Withdraw,
    TransferFrom,
    Transfer
  }

  enum BalanceType {
    Base,
    Cntr,
    Rwrd
  }

  event ClientPaymentEvent(
    address indexed client,
    ClientPaymentEventType clientPaymentEventType,
    BalanceType balanceType,
    int clientBalanceDelta
  );

  enum ClientOrderEventType {
    Create,
    Continue,
    Cancel
  }

  event ClientOrderEvent(
    address indexed client,
    ClientOrderEventType clientOrderEventType,
    uint128 orderId,
    uint maxMatches
  );

  enum MarketOrderEventType {
    // orderCount++, depth += depthBase
    Add,
    // orderCount--, depth -= depthBase
    Remove,
    // orderCount--, depth -= depthBase, traded += tradeBase
    // (depth change and traded change differ when tiny remaining amount refunded)
    CompleteFill,
    // orderCount unchanged, depth -= depthBase, traded += tradeBase
    PartialFill
  }

  // Technically not needed but these events can be used to maintain an order book or
  // watch for fills. Note that the orderId and price are those of the maker.

  event MarketOrderEvent(
    uint256 indexed eventTimestamp,
    uint128 indexed orderId,
    MarketOrderEventType marketOrderEventType,
    uint16 price,
    uint depthBase,
    uint tradeBase
  );

  // the base token (e.g. TEST)
  
  ERC20 baseToken;

  // minimum order size (inclusive)
  uint baseMinInitialSize; // set at init

  // if following partial match, the remaning gets smaller than this, remove from book and refund:
  // generally we make this 10% of baseMinInitialSize
  uint baseMinRemainingSize; // set at init

  // maximum order size (exclusive)
  // chosen so that even multiplied by the max price (or divided by the min price),
  // and then multiplied by ethRwrdRate, it still fits in 2^127, allowing us to save
  // some gas by storing executed + fee fields as uint128.
  // even with 18 decimals, this still allows order sizes up to 1,000,000,000.
  // if we encounter a token with e.g. 36 decimals we'll have to revisit ...
  uint constant baseMaxSize = 10 ** 30;

  // the counter currency (ETH)
  // (no address because it is ETH)

  // avoid the book getting cluttered up with tiny amounts not worth the gas
  uint constant cntrMinInitialSize = 10 finney;

  // see comments for baseMaxSize
  uint constant cntrMaxSize = 10 ** 30;

  // the reward token that can be used to pay fees (UBI)

  ERC20 rwrdToken; // set at init

  // used to convert ETH amount to reward tokens when paying fee with reward tokens
  uint constant ethRwrdRate = 1000;
  
  // funds that belong to clients (base, counter, and reward)

  mapping (address => uint) balanceBaseForClient;
  mapping (address => uint) balanceCntrForClient;
  mapping (address => uint) balanceRwrdForClient;

  // fee charged on liquidity taken, expressed as a divisor
  // (e.g. 2000 means 1/2000, or 0.05%)

  uint constant feeDivisor = 2000;
  
  // fees charged are given to:
  
  address feeCollector; // set at init

  // all orders ever created
  
  mapping (uint128 => Order) orderForOrderId;

  // Effectively a compact mapping from price to whether there are any open orders at that price.
  // See "Price Calculation Constants" below as to why 85.

  uint256[85] occupiedPriceBitmaps;

  // These allow us to walk over the orders in the book at a given price level (and add more).

  mapping (uint16 => OrderChain) orderChainForOccupiedPrice;
  mapping (uint128 => OrderChainNode) orderChainNodeForOpenOrderId;

  // These allow a client to (reasonably) efficiently find their own orders
  // without relying on events (which even indexed are a bit expensive to search
  // and cannot be accessed from smart contracts). See walkOrders.

  mapping (address => uint128) mostRecentOrderIdForClient;
  mapping (uint128 => uint128) clientPreviousOrderIdBeforeOrderId;

  // Price Calculation Constants.
  //
  // We pack direction and price into a crafty decimal floating point representation
  // for efficient indexing by price, the main thing we lose by doing so is precision -
  // we only have 3 significant figures in our prices.
  //
  // An unpacked price consists of:
  //
  //   direction - invalid / buy / sell
  //   mantissa  - ranges from 100 to 999 representing 0.100 to 0.999
  //   exponent  - ranges from minimumPriceExponent to minimumPriceExponent + 11
  //               (e.g. -5 to +6 for a typical pair where minPriceExponent = -5)
  //
  // The packed representation has 21601 different price values:
  //
  //      0  = invalid (can be used as marker value)
  //      1  = buy at maximum price (0.999 * 10 ** 6)
  //    ...  = other buy prices in descending order
  //   5400  = buy at 1.00
  //    ...  = other buy prices in descending order
  //  10800  = buy at minimum price (0.100 * 10 ** -5)
  //  10801  = sell at minimum price (0.100 * 10 ** -5)
  //    ...  = other sell prices in descending order
  //  16201  = sell at 1.00
  //    ...  = other sell prices in descending order
  //  21600  = sell at maximum price (0.999 * 10 ** 6)
  //  21601+ = do not use
  //
  // If we want to map each packed price to a boolean value (which we do),
  // we require 85 256-bit words. Or 42.5 for each side of the book.
  
  int8 minPriceExponent; // set at init

  uint constant invalidPrice = 0;

  // careful: max = largest unpacked value, not largest packed value
  uint constant maxBuyPrice = 1; 
  uint constant minBuyPrice = 10800;
  uint constant minSellPrice = 10801;
  uint constant maxSellPrice = 21600;

  // Constructor.
  //
  // Sets feeCollector to the creator. Creator needs to call init() to finish setup.
  //
  function BookERC20EthV1p1() {
    address creator = msg.sender;
    feeCollector = creator;
  }

  // "Public" Management - set address of base and reward tokens.
  //
  // Can only be done once (normally immediately after creation) by the fee collector.
  //
  // Used instead of a constructor to make deployment easier.
  //
  // baseMinInitialSize is the minimum order size in token-wei;
  // the minimum resting size will be one tenth of that.
  //
  // minPriceExponent controls the range of prices supported by the contract;
  // the range will be 0.100*10**minPriceExponent to 0.999*10**(minPriceExponent + 11)
  // but careful; this is in token-wei : wei, ignoring the number of decimals of the token
  // e.g. -5 implies 1 token-wei worth between 0.100e-5 to 0.999e+6 wei
  // which implies same token:eth exchange rate if token decimals are 18 like eth,
  // but if token decimals are 8, that would imply 1 token worth 10 wei to 0.000999 ETH.
  //
  function init(ERC20 _baseToken, ERC20 _rwrdToken, uint _baseMinInitialSize, int8 _minPriceExponent) public {
    require(msg.sender == feeCollector);
    require(address(baseToken) == 0);
    require(address(_baseToken) != 0);
    require(address(rwrdToken) == 0);
    require(address(_rwrdToken) != 0);
    require(_baseMinInitialSize >= 10);
    require(_baseMinInitialSize < baseMaxSize / 1000000);
    require(_minPriceExponent >= -20 && _minPriceExponent <= 20);
    if (_minPriceExponent < 2) {
      require(_baseMinInitialSize >= 10 ** uint(3-int(minPriceExponent)));
    }
    baseMinInitialSize = _baseMinInitialSize;
    // dust prevention. truncation ok, know >= 10
    baseMinRemainingSize = _baseMinInitialSize / 10;
    minPriceExponent = _minPriceExponent;
    // attempt to catch bad tokens:
    require(_baseToken.totalSupply() > 0);
    baseToken = _baseToken;
    require(_rwrdToken.totalSupply() > 0);
    rwrdToken = _rwrdToken;
  }

  // "Public" Management - change fee collector
  //
  // The new fee collector only gets fees charged after this point.
  //
  function changeFeeCollector(address newFeeCollector) public {
    address oldFeeCollector = feeCollector;
    require(msg.sender == oldFeeCollector);
    require(newFeeCollector != oldFeeCollector);
    feeCollector = newFeeCollector;
  }
  
  // Public Info View - what is being traded here, what are the limits?
  //
  function getBookInfo() public constant returns (
      BookType _bookType, address _baseToken, address _rwrdToken,
      uint _baseMinInitialSize, uint _cntrMinInitialSize, int8 _minPriceExponent,
      uint _feeDivisor, address _feeCollector
    ) {
    return (
      BookType.ERC20EthV1,
      address(baseToken),
      address(rwrdToken),
      baseMinInitialSize, // can assume min resting size is one tenth of this
      cntrMinInitialSize,
      minPriceExponent,
      feeDivisor,
      feeCollector
    );
  }

  // Public Funds View - get balances held by contract on behalf of the client,
  // or balances approved for deposit but not yet claimed by the contract.
  //
  // Excludes funds in open orders.
  //
  // Helps a web ui get a consistent snapshot of balances.
  //
  // It would be nice to return the off-exchange ETH balance too but there's a
  // bizarre bug in geth (and apparently as a result via MetaMask) that leads
  // to unpredictable behaviour when looking up client balances in constant
  // functions - see e.g. https://github.com/ethereum/solidity/issues/2325 .
  //
  function getClientBalances(address client) public constant returns (
      uint bookBalanceBase,
      uint bookBalanceCntr,
      uint bookBalanceRwrd,
      uint approvedBalanceBase,
      uint approvedBalanceRwrd,
      uint ownBalanceBase,
      uint ownBalanceRwrd
    ) {
    bookBalanceBase = balanceBaseForClient[client];
    bookBalanceCntr = balanceCntrForClient[client];
    bookBalanceRwrd = balanceRwrdForClient[client];
    approvedBalanceBase = baseToken.allowance(client, address(this));
    approvedBalanceRwrd = rwrdToken.allowance(client, address(this));
    ownBalanceBase = baseToken.balanceOf(client);
    ownBalanceRwrd = rwrdToken.balanceOf(client);
  }

  // Public Funds Manipulation - deposit previously-approved base tokens.
  //
  function transferFromBase() public {
    address client = msg.sender;
    address book = address(this);
    // we trust the ERC20 token contract not to do nasty things like call back into us -
    // if we cannot trust the token then why are we allowing it to be traded?
    uint amountBase = baseToken.allowance(client, book);
    require(amountBase > 0);
    // NB: needs change for older ERC20 tokens that don't return bool
    require(baseToken.transferFrom(client, book, amountBase));
    // belt and braces
    assert(baseToken.allowance(client, book) == 0);
    balanceBaseForClient[client] += amountBase;
    ClientPaymentEvent(client, ClientPaymentEventType.TransferFrom, BalanceType.Base, int(amountBase));
  }

  // Public Funds Manipulation - withdraw base tokens (as a transfer).
  //
  function transferBase(uint amountBase) public {
    address client = msg.sender;
    require(amountBase > 0);
    require(amountBase <= balanceBaseForClient[client]);
    // overflow safe since we checked less than balance above
    balanceBaseForClient[client] -= amountBase;
    // we trust the ERC20 token contract not to do nasty things like call back into us -
    // if we cannot trust the token then why are we allowing it to be traded?
    // NB: needs change for older ERC20 tokens that don't return bool
    require(baseToken.transfer(client, amountBase));
    ClientPaymentEvent(client, ClientPaymentEventType.Transfer, BalanceType.Base, -int(amountBase));
  }

  // Public Funds Manipulation - deposit counter currency (ETH).
  //
  function depositCntr() public payable {
    address client = msg.sender;
    uint amountCntr = msg.value;
    require(amountCntr > 0);
    // overflow safe - if someone owns pow(2,255) ETH we have bigger problems
    balanceCntrForClient[client] += amountCntr;
    ClientPaymentEvent(client, ClientPaymentEventType.Deposit, BalanceType.Cntr, int(amountCntr));
  }

  // Public Funds Manipulation - withdraw counter currency (ETH).
  //
  function withdrawCntr(uint amountCntr) public {
    address client = msg.sender;
    require(amountCntr > 0);
    require(amountCntr <= balanceCntrForClient[client]);
    // overflow safe - checked less than balance above
    balanceCntrForClient[client] -= amountCntr;
    // safe - not enough gas to do anything interesting in fallback, already adjusted balance
    client.transfer(amountCntr);
    ClientPaymentEvent(client, ClientPaymentEventType.Withdraw, BalanceType.Cntr, -int(amountCntr));
  }

  // Public Funds Manipulation - deposit previously-approved reward tokens.
  //
  function transferFromRwrd() public {
    address client = msg.sender;
    address book = address(this);
    uint amountRwrd = rwrdToken.allowance(client, book);
    require(amountRwrd > 0);
    // we wrote the reward token so we know it supports ERC20 properly and is not evil
    require(rwrdToken.transferFrom(client, book, amountRwrd));
    // belt and braces
    assert(rwrdToken.allowance(client, book) == 0);
    balanceRwrdForClient[client] += amountRwrd;
    ClientPaymentEvent(client, ClientPaymentEventType.TransferFrom, BalanceType.Rwrd, int(amountRwrd));
  }

  // Public Funds Manipulation - withdraw base tokens (as a transfer).
  //
  function transferRwrd(uint amountRwrd) public {
    address client = msg.sender;
    require(amountRwrd > 0);
    require(amountRwrd <= balanceRwrdForClient[client]);
    // overflow safe - checked less than balance above
    balanceRwrdForClient[client] -= amountRwrd;
    // we wrote the reward token so we know it supports ERC20 properly and is not evil
    require(rwrdToken.transfer(client, amountRwrd));
    ClientPaymentEvent(client, ClientPaymentEventType.Transfer, BalanceType.Rwrd, -int(amountRwrd));
  }

  // Public Order View - get full details of an order.
  //
  // If the orderId does not exist, status will be Unknown.
  //
  function getOrder(uint128 orderId) public constant returns (
    address client, uint16 price, uint sizeBase, Terms terms,
    Status status, ReasonCode reasonCode, uint executedBase, uint executedCntr,
    uint feesBaseOrCntr, uint feesRwrd) {
    Order storage order = orderForOrderId[orderId];
    return (order.client, order.price, order.sizeBase, order.terms,
            order.status, order.reasonCode, order.executedBase, order.executedCntr,
            order.feesBaseOrCntr, order.feesRwrd);
  }

  // Public Order View - get mutable details of an order.
  //
  // If the orderId does not exist, status will be Unknown.
  //
  function getOrderState(uint128 orderId) public constant returns (
    Status status, ReasonCode reasonCode, uint executedBase, uint executedCntr,
    uint feesBaseOrCntr, uint feesRwrd) {
    Order storage order = orderForOrderId[orderId];
    return (order.status, order.reasonCode, order.executedBase, order.executedCntr,
            order.feesBaseOrCntr, order.feesRwrd);
  }
  
  // Public Order View - enumerate all recent orders + all open orders for one client.
  //
  // Not really designed for use from a smart contract transaction.
  //
  // Idea is:
  //  - client ensures order ids are generated so that most-signficant part is time-based;
  //  - client decides they want all orders after a certain point-in-time,
  //    and chooses minClosedOrderIdCutoff accordingly;
  //  - before that point-in-time they just get open and needs gas orders
  //  - client calls walkClientOrders with maybeLastOrderIdReturned = 0 initially;
  //  - then repeats with the orderId returned by walkClientOrders;
  //  - (and stops if it returns a zero orderId);
  //
  // Note that client is only used when maybeLastOrderIdReturned = 0.
  //
  function walkClientOrders(
      address client, uint128 maybeLastOrderIdReturned, uint128 minClosedOrderIdCutoff
    ) public constant returns (
      uint128 orderId, uint16 price, uint sizeBase, Terms terms,
      Status status, ReasonCode reasonCode, uint executedBase, uint executedCntr,
      uint feesBaseOrCntr, uint feesRwrd) {
    if (maybeLastOrderIdReturned == 0) {
      orderId = mostRecentOrderIdForClient[client];
    } else {
      orderId = clientPreviousOrderIdBeforeOrderId[maybeLastOrderIdReturned];
    }
    while (true) {
      if (orderId == 0) return;
      Order storage order = orderForOrderId[orderId];
      if (orderId >= minClosedOrderIdCutoff) break;
      if (order.status == Status.Open || order.status == Status.NeedsGas) break;
      orderId = clientPreviousOrderIdBeforeOrderId[orderId];
    }
    return (orderId, order.price, order.sizeBase, order.terms,
            order.status, order.reasonCode, order.executedBase, order.executedCntr,
            order.feesBaseOrCntr, order.feesRwrd);
  }
 
  // Internal Price Calculation - turn packed price into a friendlier unpacked price.
  //
  function unpackPrice(uint16 price) internal constant returns (
      Direction direction, uint16 mantissa, int8 exponent
    ) {
    uint sidedPriceIndex = uint(price);
    uint priceIndex;
    if (sidedPriceIndex < 1 || sidedPriceIndex > maxSellPrice) {
      direction = Direction.Invalid;
      mantissa = 0;
      exponent = 0;
      return;
    } else if (sidedPriceIndex <= minBuyPrice) {
      direction = Direction.Buy;
      priceIndex = minBuyPrice - sidedPriceIndex;
    } else {
      direction = Direction.Sell;
      priceIndex = sidedPriceIndex - minSellPrice;
    }
    uint zeroBasedMantissa = priceIndex % 900;
    uint zeroBasedExponent = priceIndex / 900;
    mantissa = uint16(zeroBasedMantissa + 100);
    exponent = int8(zeroBasedExponent) + minPriceExponent;
    return;
  }
  
  // Internal Price Calculation - is a packed price on the buy side?
  //
  // Throws an error if price is invalid.
  //
  function isBuyPrice(uint16 price) internal constant returns (bool isBuy) {
    // yes, this looks odd, but max here is highest _unpacked_ price
    return price >= maxBuyPrice && price <= minBuyPrice;
  }
  
  // Internal Price Calculation - turn a packed buy price into a packed sell price.
  //
  // Invalid price remains invalid.
  //
  function computeOppositePrice(uint16 price) internal constant returns (uint16 opposite) {
    if (price < maxBuyPrice || price > maxSellPrice) {
      return uint16(invalidPrice);
    } else if (price <= minBuyPrice) {
      return uint16(maxSellPrice - (price - maxBuyPrice));
    } else {
      return uint16(maxBuyPrice + (maxSellPrice - price));
    }
  }
  
  // Internal Price Calculation - compute amount in counter currency that would
  // be obtained by selling baseAmount at the given unpacked price (if no fees).
  //
  // Notes:
  //  - Does not validate price - caller must ensure valid.
  //  - Could overflow producing very unexpected results if baseAmount very
  //    large - caller must check this.
  //  - This rounds the amount towards zero.
  //  - May truncate to zero if baseAmount very small - potentially allowing
  //    zero-cost buys or pointless sales - caller must check this.
  //
  function computeCntrAmountUsingUnpacked(
      uint baseAmount, uint16 mantissa, int8 exponent
    ) internal constant returns (uint cntrAmount) {
    if (exponent < 0) {
      return baseAmount * uint(mantissa) / 1000 / 10 ** uint(-exponent);
    } else {
      return baseAmount * uint(mantissa) / 1000 * 10 ** uint(exponent);
    }
  }

  // Internal Price Calculation - compute amount in counter currency that would
  // be obtained by selling baseAmount at the given packed price (if no fees).
  //
  // Notes:
  //  - Does not validate price - caller must ensure valid.
  //  - Direction of the packed price is ignored.
  //  - Could overflow producing very unexpected results if baseAmount very
  //    large - caller must check this.
  //  - This rounds the amount towards zero (regardless of Buy or Sell).
  //  - May truncate to zero if baseAmount very small - potentially allowing
  //    zero-cost buys or pointless sales - caller must check this.
  //
  function computeCntrAmountUsingPacked(
      uint baseAmount, uint16 price
    ) internal constant returns (uint) {
    var (, mantissa, exponent) = unpackPrice(price);
    return computeCntrAmountUsingUnpacked(baseAmount, mantissa, exponent);
  }

  // Public Order Placement - create order and try to match it and/or add it to the book.
  //
  function createOrder(
      uint128 orderId, uint16 price, uint sizeBase, Terms terms, uint maxMatches
    ) public {
    address client = msg.sender;
    require(orderId != 0 && orderForOrderId[orderId].client == 0);
    ClientOrderEvent(client, ClientOrderEventType.Create, orderId, maxMatches);
    orderForOrderId[orderId] =
      Order(client, price, sizeBase, terms, Status.Unknown, ReasonCode.None, 0, 0, 0, 0);
    uint128 previousMostRecentOrderIdForClient = mostRecentOrderIdForClient[client];
    mostRecentOrderIdForClient[client] = orderId;
    clientPreviousOrderIdBeforeOrderId[orderId] = previousMostRecentOrderIdForClient;
    Order storage order = orderForOrderId[orderId];
    var (direction, mantissa, exponent) = unpackPrice(price);
    if (direction == Direction.Invalid) {
      order.status = Status.Rejected;
      order.reasonCode = ReasonCode.InvalidPrice;
      return;
    }
    if (sizeBase < baseMinInitialSize || sizeBase > baseMaxSize) {
      order.status = Status.Rejected;
      order.reasonCode = ReasonCode.InvalidSize;
      return;
    }
    uint sizeCntr = computeCntrAmountUsingUnpacked(sizeBase, mantissa, exponent);
    if (sizeCntr < cntrMinInitialSize || sizeCntr > cntrMaxSize) {
      order.status = Status.Rejected;
      order.reasonCode = ReasonCode.InvalidSize;
      return;
    }
    if (terms == Terms.MakerOnly && maxMatches != 0) {
      order.status = Status.Rejected;
      order.reasonCode = ReasonCode.InvalidTerms;
      return;
    }
    if (!debitFunds(client, direction, sizeBase, sizeCntr)) {
      order.status = Status.Rejected;
      order.reasonCode = ReasonCode.InsufficientFunds;
      return;
    }
    processOrder(orderId, maxMatches);
  }

  // Public Order Placement - cancel order
  //
  function cancelOrder(uint128 orderId) public {
    address client = msg.sender;
    Order storage order = orderForOrderId[orderId];
    require(order.client == client);
    Status status = order.status;
    if (status != Status.Open && status != Status.NeedsGas) {
      return;
    }
    ClientOrderEvent(client, ClientOrderEventType.Cancel, orderId, 0);
    if (status == Status.Open) {
      removeOpenOrderFromBook(orderId);
      MarketOrderEvent(block.timestamp, orderId, MarketOrderEventType.Remove, order.price,
        order.sizeBase - order.executedBase, 0);
    }
    refundUnmatchedAndFinish(orderId, Status.Done, ReasonCode.ClientCancel);
  }

  // Public Order Placement - continue placing an order in 'NeedsGas' state
  //
  function continueOrder(uint128 orderId, uint maxMatches) public {
    address client = msg.sender;
    Order storage order = orderForOrderId[orderId];
    require(order.client == client);
    if (order.status != Status.NeedsGas) {
      return;
    }
    ClientOrderEvent(client, ClientOrderEventType.Continue, orderId, maxMatches);
    order.status = Status.Unknown;
    processOrder(orderId, maxMatches);
  }

  // Internal Order Placement - remove a still-open order from the book.
  //
  // Caller's job to update/refund the order + raise event, this just
  // updates the order chain and bitmask.
  //
  // Too expensive to do on each resting order match - we only do this for an
  // order being cancelled. See matchWithOccupiedPrice for similar logic.
  //
  function removeOpenOrderFromBook(uint128 orderId) internal {
    Order storage order = orderForOrderId[orderId];
    uint16 price = order.price;
    OrderChain storage orderChain = orderChainForOccupiedPrice[price];
    OrderChainNode storage orderChainNode = orderChainNodeForOpenOrderId[orderId];
    uint128 nextOrderId = orderChainNode.nextOrderId;
    uint128 prevOrderId = orderChainNode.prevOrderId;
    if (nextOrderId != 0) {
      OrderChainNode storage nextOrderChainNode = orderChainNodeForOpenOrderId[nextOrderId];
      nextOrderChainNode.prevOrderId = prevOrderId;
    } else {
      orderChain.lastOrderId = prevOrderId;
    }
    if (prevOrderId != 0) {
      OrderChainNode storage prevOrderChainNode = orderChainNodeForOpenOrderId[prevOrderId];
      prevOrderChainNode.nextOrderId = nextOrderId;
    } else {
      orderChain.firstOrderId = nextOrderId;
    }
    if (nextOrderId == 0 && prevOrderId == 0) {
      uint bmi = price / 256;  // index into array of bitmaps
      uint bti = price % 256;  // bit position within bitmap
      // we know was previously occupied so XOR clears
      occupiedPriceBitmaps[bmi] ^= 2 ** bti;
    }
  }

  // Internal Order Placement - credit funds received when taking liquidity from book
  //
  function creditExecutedFundsLessFees(uint128 orderId, uint originalExecutedBase, uint originalExecutedCntr) internal {
    Order storage order = orderForOrderId[orderId];
    uint liquidityTakenBase = order.executedBase - originalExecutedBase;
    uint liquidityTakenCntr = order.executedCntr - originalExecutedCntr;
    // Normally we deduct the fee from the currency bought (base for buy, cntr for sell),
    // however we also accept reward tokens from the reward balance if it covers the fee,
    // with the reward amount converted from the ETH amount (the counter currency here)
    // at a fixed exchange rate.
    // Overflow safe since we ensure order size < 10^30 in both currencies (see baseMaxSize).
    // Can truncate to zero, which is fine.
    uint feesRwrd = liquidityTakenCntr / feeDivisor * ethRwrdRate;
    uint feesBaseOrCntr;
    address client = order.client;
    uint availRwrd = balanceRwrdForClient[client];
    if (feesRwrd <= availRwrd) {
      balanceRwrdForClient[client] = availRwrd - feesRwrd;
      balanceRwrdForClient[feeCollector] = feesRwrd;
      // Need += rather than = because could have paid some fees earlier in NeedsGas situation.
      // Overflow safe since we ensure order size < 10^30 in both currencies (see baseMaxSize).
      // Can truncate to zero, which is fine.
      order.feesRwrd += uint128(feesRwrd);
      if (isBuyPrice(order.price)) {
        balanceBaseForClient[client] += liquidityTakenBase;
      } else {
        balanceCntrForClient[client] += liquidityTakenCntr;
      }
    } else if (isBuyPrice(order.price)) {
      // See comments in branch above re: use of += and overflow safety.
      feesBaseOrCntr = liquidityTakenBase / feeDivisor;
      balanceBaseForClient[order.client] += (liquidityTakenBase - feesBaseOrCntr);
      order.feesBaseOrCntr += uint128(feesBaseOrCntr);
      balanceBaseForClient[feeCollector] += feesBaseOrCntr;
    } else {
      // See comments in branch above re: use of += and overflow safety.
      feesBaseOrCntr = liquidityTakenCntr / feeDivisor;
      balanceCntrForClient[order.client] += (liquidityTakenCntr - feesBaseOrCntr);
      order.feesBaseOrCntr += uint128(feesBaseOrCntr);
      balanceCntrForClient[feeCollector] += feesBaseOrCntr;
    }
  }

  // Internal Order Placement - process a created and sanity checked order.
  //
  // Used both for new orders and for gas topup.
  //
  function processOrder(uint128 orderId, uint maxMatches) internal {
    Order storage order = orderForOrderId[orderId];

    uint ourOriginalExecutedBase = order.executedBase;
    uint ourOriginalExecutedCntr = order.executedCntr;

    var (ourDirection,) = unpackPrice(order.price);
    uint theirPriceStart = (ourDirection == Direction.Buy) ? minSellPrice : maxBuyPrice;
    uint theirPriceEnd = computeOppositePrice(order.price);
   
    MatchStopReason matchStopReason =
      matchAgainstBook(orderId, theirPriceStart, theirPriceEnd, maxMatches);

    creditExecutedFundsLessFees(orderId, ourOriginalExecutedBase, ourOriginalExecutedCntr);

    if (order.terms == Terms.ImmediateOrCancel) {
      if (matchStopReason == MatchStopReason.Satisfied) {
        refundUnmatchedAndFinish(orderId, Status.Done, ReasonCode.None);
        return;
      } else if (matchStopReason == MatchStopReason.MaxMatches) {
        refundUnmatchedAndFinish(orderId, Status.Done, ReasonCode.TooManyMatches);
        return;
      } else if (matchStopReason == MatchStopReason.BookExhausted) {
        refundUnmatchedAndFinish(orderId, Status.Done, ReasonCode.Unmatched);
        return;
      }
    } else if (order.terms == Terms.MakerOnly) {
      if (matchStopReason == MatchStopReason.MaxMatches) {
        refundUnmatchedAndFinish(orderId, Status.Rejected, ReasonCode.WouldTake);
        return;
      } else if (matchStopReason == MatchStopReason.BookExhausted) {
        enterOrder(orderId);
        return;
      }
    } else if (order.terms == Terms.GTCNoGasTopup) {
      if (matchStopReason == MatchStopReason.Satisfied) {
        refundUnmatchedAndFinish(orderId, Status.Done, ReasonCode.None);
        return;
      } else if (matchStopReason == MatchStopReason.MaxMatches) {
        refundUnmatchedAndFinish(orderId, Status.Done, ReasonCode.TooManyMatches);
        return;
      } else if (matchStopReason == MatchStopReason.BookExhausted) {
        enterOrder(orderId);
        return;
      }
    } else if (order.terms == Terms.GTCWithGasTopup) {
      if (matchStopReason == MatchStopReason.Satisfied) {
        refundUnmatchedAndFinish(orderId, Status.Done, ReasonCode.None);
        return;
      } else if (matchStopReason == MatchStopReason.MaxMatches) {
        order.status = Status.NeedsGas;
        return;
      } else if (matchStopReason == MatchStopReason.BookExhausted) {
        enterOrder(orderId);
        return;
      }
    }
    assert(false); // should not be possible to reach here
  }
 
  // Used internally to indicate why we stopped matching an order against the book.

  enum MatchStopReason {
    None,
    MaxMatches,
    Satisfied,
    PriceExhausted,
    BookExhausted
  }
 
  // Internal Order Placement - Match the given order against the book.
  //
  // Resting orders matched will be updated, removed from book and funds credited to their owners.
  //
  // Only updates the executedBase and executedCntr of the given order - caller is responsible
  // for crediting matched funds, charging fees, marking order as done / entering it into the book.
  //
  // matchStopReason returned will be one of MaxMatches, Satisfied or BookExhausted.
  //
  // Calling with maxMatches == 0 is ok - and expected when the order is a maker-only order.
  //
  function matchAgainstBook(
      uint128 orderId, uint theirPriceStart, uint theirPriceEnd, uint maxMatches
    ) internal returns (
      MatchStopReason matchStopReason
    ) {
    Order storage order = orderForOrderId[orderId];
    
    uint bmi = theirPriceStart / 256;  // index into array of bitmaps
    uint bti = theirPriceStart % 256;  // bit position within bitmap
    uint bmiEnd = theirPriceEnd / 256; // last bitmap to search
    uint btiEnd = theirPriceEnd % 256; // stop at this bit in the last bitmap

    uint cbm = occupiedPriceBitmaps[bmi]; // original copy of current bitmap
    uint dbm = cbm; // dirty version of current bitmap where we may have cleared bits
    uint wbm = cbm >> bti; // working copy of current bitmap which we keep shifting
    
    // these loops are pretty ugly, and somewhat unpredicatable in terms of gas,
    // ... but no-one else has come up with a better matching engine yet!

    bool removedLastAtPrice;
    matchStopReason = MatchStopReason.None;

    while (bmi < bmiEnd) {
      if (wbm == 0 || bti == 256) {
        if (dbm != cbm) {
          occupiedPriceBitmaps[bmi] = dbm;
        }
        bti = 0;
        bmi++;
        cbm = occupiedPriceBitmaps[bmi];
        wbm = cbm;
        dbm = cbm;
      } else {
        if ((wbm & 1) != 0) {
          // careful - copy-and-pasted in loop below ...
          (removedLastAtPrice, maxMatches, matchStopReason) =
            matchWithOccupiedPrice(order, uint16(bmi * 256 + bti), maxMatches);
          if (removedLastAtPrice) {
            dbm ^= 2 ** bti;
          }
          if (matchStopReason == MatchStopReason.PriceExhausted) {
            matchStopReason = MatchStopReason.None;
          } else if (matchStopReason != MatchStopReason.None) {
            // we might still have changes in dbm to write back - see later
            break;
          }
        }
        bti += 1;
        wbm /= 2;
      }
    }
    if (matchStopReason == MatchStopReason.None) {
      // we've reached the last bitmap we need to search,
      // we'll stop at btiEnd not 256 this time.
      while (bti <= btiEnd && wbm != 0) {
        if ((wbm & 1) != 0) {
          // careful - copy-and-pasted in loop above ...
          (removedLastAtPrice, maxMatches, matchStopReason) =
            matchWithOccupiedPrice(order, uint16(bmi * 256 + bti), maxMatches);
          if (removedLastAtPrice) {
            dbm ^= 2 ** bti;
          }
          if (matchStopReason == MatchStopReason.PriceExhausted) {
            matchStopReason = MatchStopReason.None;
          } else if (matchStopReason != MatchStopReason.None) {
            break;
          }
        }
        bti += 1;
        wbm /= 2;
      }
    }
    // Careful - if we exited the first loop early, or we went into the second loop,
    // (luckily can't both happen) then we haven't flushed the dirty bitmap back to
    // storage - do that now if we need to.
    if (dbm != cbm) {
      occupiedPriceBitmaps[bmi] = dbm;
    }
    if (matchStopReason == MatchStopReason.None) {
      matchStopReason = MatchStopReason.BookExhausted;
    }
  }

  // Internal Order Placement.
  //
  // Match our order against up to maxMatches resting orders at the given price (which
  // is known by the caller to have at least one resting order).
  //
  // The matches (partial or complete) of the resting orders are recorded, and their
  // funds are credited.
  //
  // The order chain for the resting orders is updated, but the occupied price bitmap is NOT -
  // the caller must clear the relevant bit if removedLastAtPrice = true is returned.
  //
  // Only updates the executedBase and executedCntr of our order - caller is responsible
  // for e.g. crediting our matched funds, updating status.
  //
  // Calling with maxMatches == 0 is ok - and expected when the order is a maker-only order.
  //
  // Returns:
  //   removedLastAtPrice:
  //     true iff there are no longer any resting orders at this price - caller will need
  //     to update the occupied price bitmap.
  //
  //   matchesLeft:
  //     maxMatches passed in minus the number of matches made by this call
  //
  //   matchStopReason:
  //     If our order is completely matched, matchStopReason will be Satisfied.
  //     If our order is not completely matched, matchStopReason will be either:
  //        MaxMatches (we are not allowed to match any more times)
  //     or:
  //        PriceExhausted (nothing left on the book at this exact price)
  //
  function matchWithOccupiedPrice(
      Order storage ourOrder, uint16 theirPrice, uint maxMatches
    ) internal returns (
    bool removedLastAtPrice, uint matchesLeft, MatchStopReason matchStopReason) {
    matchesLeft = maxMatches;
    uint workingOurExecutedBase = ourOrder.executedBase;
    uint workingOurExecutedCntr = ourOrder.executedCntr;
    uint128 theirOrderId = orderChainForOccupiedPrice[theirPrice].firstOrderId;
    matchStopReason = MatchStopReason.None;
    while (true) {
      if (matchesLeft == 0) {
        matchStopReason = MatchStopReason.MaxMatches;
        break;
      }
      uint matchBase;
      uint matchCntr;
      (theirOrderId, matchBase, matchCntr, matchStopReason) =
        matchWithTheirs((ourOrder.sizeBase - workingOurExecutedBase), theirOrderId, theirPrice);
      workingOurExecutedBase += matchBase;
      workingOurExecutedCntr += matchCntr;
      matchesLeft -= 1;
      if (matchStopReason != MatchStopReason.None) {
        break;
      }
    }
    ourOrder.executedBase = uint128(workingOurExecutedBase);
    ourOrder.executedCntr = uint128(workingOurExecutedCntr);
    if (theirOrderId == 0) {
      orderChainForOccupiedPrice[theirPrice].firstOrderId = 0;
      orderChainForOccupiedPrice[theirPrice].lastOrderId = 0;
      removedLastAtPrice = true;
    } else {
      // NB: in some cases (e.g. maxMatches == 0) this is a no-op.
      orderChainForOccupiedPrice[theirPrice].firstOrderId = theirOrderId;
      orderChainNodeForOpenOrderId[theirOrderId].prevOrderId = 0;
      removedLastAtPrice = false;
    }
  }
  
  // Internal Order Placement.
  //
  // Match up to our remaining amount against a resting order in the book.
  //
  // The match (partial, complete or effectively-complete) of the resting order
  // is recorded, and their funds are credited.
  //
  // Their order is NOT removed from the book by this call - the caller must do that
  // if the nextTheirOrderId returned is not equal to the theirOrderId passed in.
  //
  // Returns:
  //
  //   nextTheirOrderId:
  //     If we did not completely match their order, will be same as theirOrderId.
  //     If we completely matched their order, will be orderId of next order at the
  //     same price - or zero if this was the last order and we've now filled it.
  //
  //   matchStopReason:
  //     If our order is completely matched, matchStopReason will be Satisfied.
  //     If our order is not completely matched, matchStopReason will be either
  //     PriceExhausted (if nothing left at this exact price) or None (if can continue).
  // 
  function matchWithTheirs(
    uint ourRemainingBase, uint128 theirOrderId, uint16 theirPrice) internal returns (
    uint128 nextTheirOrderId, uint matchBase, uint matchCntr, MatchStopReason matchStopReason) {
    Order storage theirOrder = orderForOrderId[theirOrderId];
    uint theirRemainingBase = theirOrder.sizeBase - theirOrder.executedBase;
    if (ourRemainingBase < theirRemainingBase) {
      matchBase = ourRemainingBase;
    } else {
      matchBase = theirRemainingBase;
    }
    matchCntr = computeCntrAmountUsingPacked(matchBase, theirPrice);
    // It may seem a bit odd to stop here if our remaining amount is very small -
    // there could still be resting orders we can match it against. But the gas
    // cost of matching each order is quite high - potentially high enough to
    // wipe out the profit the taker hopes for from trading the tiny amount left.
    if ((ourRemainingBase - matchBase) < baseMinRemainingSize) {
      matchStopReason = MatchStopReason.Satisfied;
    } else {
      matchStopReason = MatchStopReason.None;
    }
    bool theirsDead = recordTheirMatch(theirOrder, theirOrderId, theirPrice, matchBase, matchCntr);
    if (theirsDead) {
      nextTheirOrderId = orderChainNodeForOpenOrderId[theirOrderId].nextOrderId;
      if (matchStopReason == MatchStopReason.None && nextTheirOrderId == 0) {
        matchStopReason = MatchStopReason.PriceExhausted;
      }
    } else {
      nextTheirOrderId = theirOrderId;
    }
  }

  // Internal Order Placement.
  //
  // Record match (partial or complete) of resting order, and credit them their funds.
  //
  // If their order is completely matched, the order is marked as done,
  // and "theirsDead" is returned as true.
  //
  // The order is NOT removed from the book by this call - the caller
  // must do that if theirsDead is true.
  //
  // No sanity checks are made - the caller must be sure the order is
  // not already done and has sufficient remaining. (Yes, we'd like to
  // check here too but we cannot afford the gas).
  //
  function recordTheirMatch(
      Order storage theirOrder, uint128 theirOrderId, uint16 theirPrice, uint matchBase, uint matchCntr
    ) internal returns (bool theirsDead) {
    // they are a maker so no fees
    // overflow safe - see comments about baseMaxSize
    // executedBase cannot go > sizeBase due to logic in matchWithTheirs
    theirOrder.executedBase += uint128(matchBase);
    theirOrder.executedCntr += uint128(matchCntr);
    if (isBuyPrice(theirPrice)) {
      // they have bought base (using the counter they already paid when creating the order)
      balanceBaseForClient[theirOrder.client] += matchBase;
    } else {
      // they have bought counter (using the base they already paid when creating the order)
      balanceCntrForClient[theirOrder.client] += matchCntr;
    }
    uint stillRemainingBase = theirOrder.sizeBase - theirOrder.executedBase;
    // avoid leaving tiny amounts in the book - refund remaining if too small
    if (stillRemainingBase < baseMinRemainingSize) {
      refundUnmatchedAndFinish(theirOrderId, Status.Done, ReasonCode.None);
      // someone building an UI on top needs to know how much was match and how much was refund
      MarketOrderEvent(block.timestamp, theirOrderId, MarketOrderEventType.CompleteFill,
        theirPrice, matchBase + stillRemainingBase, matchBase);
      return true;
    } else {
      MarketOrderEvent(block.timestamp, theirOrderId, MarketOrderEventType.PartialFill,
        theirPrice, matchBase, matchBase);
      return false;
    }
  }

  // Internal Order Placement.
  //
  // Refund any unmatched funds in an order (based on executed vs size) and move to a final state.
  //
  // The order is NOT removed from the book by this call and no event is raised.
  //
  // No sanity checks are made - the caller must be sure the order has not already been refunded.
  //
  function refundUnmatchedAndFinish(uint128 orderId, Status status, ReasonCode reasonCode) internal {
    Order storage order = orderForOrderId[orderId];
    uint16 price = order.price;
    if (isBuyPrice(price)) {
      uint sizeCntr = computeCntrAmountUsingPacked(order.sizeBase, price);
      balanceCntrForClient[order.client] += sizeCntr - order.executedCntr;
    } else {
      balanceBaseForClient[order.client] += order.sizeBase - order.executedBase;
    }
    order.status = status;
    order.reasonCode = reasonCode;
  }

  // Internal Order Placement.
  //
  // Enter a not completely matched order into the book, marking the order as open.
  //
  // This updates the occupied price bitmap and chain.
  //
  // No sanity checks are made - the caller must be sure the order
  // has some unmatched amount and has been paid for!
  //
  function enterOrder(uint128 orderId) internal {
    Order storage order = orderForOrderId[orderId];
    uint16 price = order.price;
    OrderChain storage orderChain = orderChainForOccupiedPrice[price];
    OrderChainNode storage orderChainNode = orderChainNodeForOpenOrderId[orderId];
    if (orderChain.firstOrderId == 0) {
      orderChain.firstOrderId = orderId;
      orderChain.lastOrderId = orderId;
      orderChainNode.nextOrderId = 0;
      orderChainNode.prevOrderId = 0;
      uint bitmapIndex = price / 256;
      uint bitIndex = price % 256;
      occupiedPriceBitmaps[bitmapIndex] |= (2 ** bitIndex);
    } else {
      uint128 existingLastOrderId = orderChain.lastOrderId;
      OrderChainNode storage existingLastOrderChainNode = orderChainNodeForOpenOrderId[existingLastOrderId];
      orderChainNode.nextOrderId = 0;
      orderChainNode.prevOrderId = existingLastOrderId;
      existingLastOrderChainNode.nextOrderId = orderId;
      orderChain.lastOrderId = orderId;
    }
    MarketOrderEvent(block.timestamp, orderId, MarketOrderEventType.Add,
      price, order.sizeBase - order.executedBase, 0);
    order.status = Status.Open;
  }

  // Internal Order Placement.
  //
  // Charge the client for the cost of placing an order in the given direction.
  //
  // Return true if successful, false otherwise.
  //
  function debitFunds(
      address client, Direction direction, uint sizeBase, uint sizeCntr
    ) internal returns (bool success) {
    if (direction == Direction.Buy) {
      uint availableCntr = balanceCntrForClient[client];
      if (availableCntr < sizeCntr) {
        return false;
      }
      balanceCntrForClient[client] = availableCntr - sizeCntr;
      return true;
    } else if (direction == Direction.Sell) {
      uint availableBase = balanceBaseForClient[client];
      if (availableBase < sizeBase) {
        return false;
      }
      balanceBaseForClient[client] = availableBase - sizeBase;
      return true;
    } else {
      return false;
    }
  }

  // Public Book View
  // 
  // Intended for public book depth enumeration from web3 (or similar).
  //
  // Not suitable for use from a smart contract transaction - gas usage
  // could be very high if we have many orders at the same price.
  //
  // Start at the given inclusive price (and side) and walk down the book
  // (getting less aggressive) until we find some open orders or reach the
  // least aggressive price.
  //
  // Returns the price where we found the order(s), the depth at that price
  // (zero if none found), order count there, and the current blockNumber.
  //
  // (The blockNumber is handy if you're taking a snapshot which you intend
  //  to keep up-to-date with the market order events).
  //
  // To walk the book, the caller should start by calling walkBook with the
  // most aggressive buy price (Buy @ 999000).
  // If the price returned is the least aggressive buy price (Buy @ 0.000001),
  // the side is complete.
  // Otherwise, call walkBook again with the (packed) price returned + 1.
  // Then repeat for the sell side, starting with Sell @ 0.000001 and stopping
  // when Sell @ 999000 is returned.
  //
  function walkBook(uint16 fromPrice) public constant returns (
      uint16 price, uint depthBase, uint orderCount, uint blockNumber
    ) {
    uint priceStart = fromPrice;
    uint priceEnd = (isBuyPrice(fromPrice)) ? minBuyPrice : maxSellPrice;
    
    // See comments in matchAgainstBook re: how these crazy loops work.
    
    uint bmi = priceStart / 256;
    uint bti = priceStart % 256;
    uint bmiEnd = priceEnd / 256;
    uint btiEnd = priceEnd % 256;

    uint wbm = occupiedPriceBitmaps[bmi] >> bti;
    
    while (bmi < bmiEnd) {
      if (wbm == 0 || bti == 256) {
        bti = 0;
        bmi++;
        wbm = occupiedPriceBitmaps[bmi];
      } else {
        if ((wbm & 1) != 0) {
          // careful - copy-pasted in below loop
          price = uint16(bmi * 256 + bti);
          (depthBase, orderCount) = sumDepth(orderChainForOccupiedPrice[price].firstOrderId);
          return (price, depthBase, orderCount, block.number);
        }
        bti += 1;
        wbm /= 2;
      }
    }
    // we've reached the last bitmap we need to search, stop at btiEnd not 256 this time.
    while (bti <= btiEnd && wbm != 0) {
      if ((wbm & 1) != 0) {
        // careful - copy-pasted in above loop
        price = uint16(bmi * 256 + bti);
        (depthBase, orderCount) = sumDepth(orderChainForOccupiedPrice[price].firstOrderId);
        return (price, depthBase, orderCount, block.number);
      }
      bti += 1;
      wbm /= 2;
    }
    return (uint16(priceEnd), 0, 0, block.number);
  }

  // Internal Book View.
  //
  // See walkBook - adds up open depth at a price starting from an
  // order which is assumed to be open. Careful - unlimited gas use.
  //
  function sumDepth(uint128 orderId) internal constant returns (uint depth, uint orderCount) {
    while (true) {
      Order storage order = orderForOrderId[orderId];
      depth += order.sizeBase - order.executedBase;
      orderCount++;
      orderId = orderChainNodeForOpenOrderId[orderId].nextOrderId;
      if (orderId == 0) {
        return (depth, orderCount);
      }
    }
  }
}

// helper for automating book creation
contract BookERC20EthV1p1Factory {

    event BookCreated (address bookAddress);

    function BookERC20EthV1p1Factory() {
    }

    function createBook(ERC20 _baseToken, ERC20 _rwrdToken, address _feeCollector, uint _baseMinInitialSize, int8 _minPriceExponent) public {
        BookERC20EthV1p1 book = new BookERC20EthV1p1();
        book.init(_baseToken, _rwrdToken, _baseMinInitialSize, _minPriceExponent);
        book.changeFeeCollector(_feeCollector);
        BookCreated(address(book));
    }
}

Contract Security Audit

Contract ABI

[{"constant":true,"inputs":[{"name":"fromPrice","type":"uint16"}],"name":"walkBook","outputs":[{"name":"price","type":"uint16"},{"name":"depthBase","type":"uint256"},{"name":"orderCount","type":"uint256"},{"name":"blockNumber","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"orderId","type":"uint128"}],"name":"getOrder","outputs":[{"name":"client","type":"address"},{"name":"price","type":"uint16"},{"name":"sizeBase","type":"uint256"},{"name":"terms","type":"uint8"},{"name":"status","type":"uint8"},{"name":"reasonCode","type":"uint8"},{"name":"executedBase","type":"uint256"},{"name":"executedCntr","type":"uint256"},{"name":"feesBaseOrCntr","type":"uint256"},{"name":"feesRwrd","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"amountCntr","type":"uint256"}],"name":"withdrawCntr","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"getBookInfo","outputs":[{"name":"_bookType","type":"uint8"},{"name":"_baseToken","type":"address"},{"name":"_rwrdToken","type":"address"},{"name":"_baseMinInitialSize","type":"uint256"},{"name":"_cntrMinInitialSize","type":"uint256"},{"name":"_minPriceExponent","type":"int8"},{"name":"_feeDivisor","type":"uint256"},{"name":"_feeCollector","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"orderId","type":"uint128"}],"name":"getOrderState","outputs":[{"name":"status","type":"uint8"},{"name":"reasonCode","type":"uint8"},{"name":"executedBase","type":"uint256"},{"name":"executedCntr","type":"uint256"},{"name":"feesBaseOrCntr","type":"uint256"},{"name":"feesRwrd","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"amountBase","type":"uint256"}],"name":"transferBase","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_baseToken","type":"address"},{"name":"_rwrdToken","type":"address"},{"name":"_baseMinInitialSize","type":"uint256"},{"name":"_minPriceExponent","type":"int8"}],"name":"init","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"amountRwrd","type":"uint256"}],"name":"transferRwrd","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"depositCntr","outputs":[],"payable":true,"type":"function"},{"constant":false,"inputs":[],"name":"transferFromBase","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"client","type":"address"}],"name":"getClientBalances","outputs":[{"name":"bookBalanceBase","type":"uint256"},{"name":"bookBalanceCntr","type":"uint256"},{"name":"bookBalanceRwrd","type":"uint256"},{"name":"approvedBalanceBase","type":"uint256"},{"name":"approvedBalanceRwrd","type":"uint256"},{"name":"ownBalanceBase","type":"uint256"},{"name":"ownBalanceRwrd","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"newFeeCollector","type":"address"}],"name":"changeFeeCollector","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"transferFromRwrd","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"client","type":"address"},{"name":"maybeLastOrderIdReturned","type":"uint128"},{"name":"minClosedOrderIdCutoff","type":"uint128"}],"name":"walkClientOrders","outputs":[{"name":"orderId","type":"uint128"},{"name":"price","type":"uint16"},{"name":"sizeBase","type":"uint256"},{"name":"terms","type":"uint8"},{"name":"status","type":"uint8"},{"name":"reasonCode","type":"uint8"},{"name":"executedBase","type":"uint256"},{"name":"executedCntr","type":"uint256"},{"name":"feesBaseOrCntr","type":"uint256"},{"name":"feesRwrd","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"orderId","type":"uint128"},{"name":"price","type":"uint16"},{"name":"sizeBase","type":"uint256"},{"name":"terms","type":"uint8"},{"name":"maxMatches","type":"uint256"}],"name":"createOrder","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"orderId","type":"uint128"},{"name":"maxMatches","type":"uint256"}],"name":"continueOrder","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"orderId","type":"uint128"}],"name":"cancelOrder","outputs":[],"payable":false,"type":"function"},{"inputs":[],"payable":false,"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"address"},{"indexed":false,"name":"clientPaymentEventType","type":"uint8"},{"indexed":false,"name":"balanceType","type":"uint8"},{"indexed":false,"name":"clientBalanceDelta","type":"int256"}],"name":"ClientPaymentEvent","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"client","type":"address"},{"indexed":false,"name":"clientOrderEventType","type":"uint8"},{"indexed":false,"name":"orderId","type":"uint128"},{"indexed":false,"name":"maxMatches","type":"uint256"}],"name":"ClientOrderEvent","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"eventTimestamp","type":"uint256"},{"indexed":true,"name":"orderId","type":"uint128"},{"indexed":false,"name":"marketOrderEventType","type":"uint8"},{"indexed":false,"name":"price","type":"uint16"},{"indexed":false,"name":"depthBase","type":"uint256"},{"indexed":false,"name":"tradeBase","type":"uint256"}],"name":"MarketOrderEvent","type":"event"}]

Deployed Bytecode

0x606060405236156100ee5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166303adcbd281146100f0578063117d412814610132578063199f0791146101db57806322ea2d96146101f057806331f9a2111461026a5780633c13fc33146102db5780633e325589146102f05780634dc7d31b1461031d578063604451421461033257806361ea6ed71461033c5780636f03e4f91461034e5780639245290d146103a2578063929ba8bf146103c0578063a87d8b6b146103d2578063bbec37681461048d578063bd5acbd6146104be578063dbc91396146104df575bfe5b34156100f857fe5b61010761ffff600435166104fd565b6040805161ffff90951685526020850193909352838301919091526060830152519081900360800190f35b341561013a57fe5b61014e6001608060020a036004351661068a565b60408051600160a060020a038c16815261ffff8b1660208201529081018990526060810188600381111561017e57fe5b60ff16815260200187600781111561019257fe5b60ff1681526020018660088111156101a657fe5b60ff1681526020018581526020018481526020018381526020018281526020019a505050505050505050505060405180910390f35b34156101e357fe5b6101ee600435610716565b005b34156101f857fe5b6102006107ef565b6040518089600081111561021057fe5b60ff168152600160a060020a0398891660208201529688166040808901919091526060880196909652506080860193909352600091820b90910b60a085015260c084015290921660e0820152905190819003610100019150f35b341561027257fe5b6102866001608060020a036004351661082b565b6040518087600781111561029657fe5b60ff1681526020018660088111156102aa57fe5b60ff168152602001858152602001848152602001838152602001828152602001965050505050505060405180910390f35b34156102e357fe5b6101ee60043561088f565b005b34156102f857fe5b6101ee600160a060020a036004358116906024351660443560643560000b6109d3565b005b341561032557fe5b6101ee600435610c43565b005b6101ee610d84565b005b341561034457fe5b6101ee610e05565b005b341561035657fe5b61036a600160a060020a036004351661100e565b604080519788526020880196909652868601949094526060860192909252608085015260a084015260c0830152519081900360e00190f35b34156103aa57fe5b6101ee600160a060020a0360043516611231565b005b34156103c857fe5b6101ee611295565b005b34156103da57fe5b610400600160a060020a03600435166001608060020a03602435811690604435166114ab565b604080516001608060020a038c16815261ffff8b1660208201529081018990526060810188600381111561017e57fe5b60ff16815260200187600781111561019257fe5b60ff1681526020018660088111156101a657fe5b60ff1681526020018581526020018481526020018381526020018281526020019a505050505050505050505060405180910390f35b341561049557fe5b6101ee6001608060020a036004351661ffff6024351660443560ff60643516608435611632565b005b34156104c657fe5b6101ee6001608060020a0360043516602435611c01565b005b34156104e757fe5b6101ee6001608060020a0360043516611cee565b005b600080808061ffff85168180808080806105168c611e7c565b61052257615460610526565b612a305b9550610100875b049450610100875b069350610100865b049250610100865b069150836009866055811061055657fe5b0160005b50549060020a900490505b828510156105fe5780158061057b575083610100145b156105a457600190940193600093506009856055811061059757fe5b0160005b505490506105f9565b60018116156105ea576101008502840161ffff81166000908152605e6020526040902054909b506105dd906001608060020a0316611ea1565b909a50985043975061067c565b600193909301926002815b0490505b610565565b5b81841115801561060e57508015155b1561066c576001811615610659576101008502840161ffff81166000908152605e6020526040902054909b506105dd906001608060020a0316611ea1565b909a50985043975061067c565b600193909301926002815b0490506105fe565b9499506000985088975043965089945b505050505050509193509193565b6001608060020a03818116600090815260086020526040902080546001820154600283015460038401546004850154600160a060020a0385169660a060020a90950461ffff1695939460ff80851695610100860482169562010000810490921694630100000090920483169382841693608060020a90930483169216905b509193959799509193959799565b33600082116107255760006000fd5b600160a060020a03811660009081526005602052604090205482111561074b5760006000fd5b600160a060020a038116600081815260056020526040808220805486900390555184156108fc0291859190818181858888f19350505050151561078a57fe5b80600160a060020a03166000805160206130d08339815191526001600185600003604051808460038111156107bb57fe5b60ff1681526020018360028111156107cf57fe5b60ff168152602001828152602001935050505060405180910390a25b5050565b60008054600354600154606254600754600160a060020a039485169493841693662386f26fc1000092870b916107d091165b9091929394959697565b6001608060020a03808216600090815260086020526040902060028101546003820154600483015460ff610100840481169562010000850490911694630100000090940481169383821693608060020a9004821692909116905b5091939550919395565b336000821161089e5760006000fd5b600160a060020a0381166000908152600460205260409020548211156108c45760006000fd5b600160a060020a0380821660008181526004602081815260408084208054899003905583548151830185905281517fa9059cbb000000000000000000000000000000000000000000000000000000008152938401959095526024830188905251939094169363a9059cbb9360448084019492939192918390030190829087803b151561094c57fe5b6102c65a03f1151561095a57fe5b5050604051511515905061096e5760006000fd5b80600160a060020a03166000805160206130d08339815191526003600085600003604051808460038111156107bb57fe5b60ff1681526020018360028111156107cf57fe5b60ff168152602001828152602001935050505060405180910390a25b5050565b60075433600160a060020a039081169116146109ef5760006000fd5b600054600160a060020a031615610a065760006000fd5b600160a060020a0384161515610a1c5760006000fd5b600354600160a060020a031615610a335760006000fd5b600160a060020a0383161515610a495760006000fd5b600a821015610a585760006000fd5b620f42406c0c9f2c9cd04674edea400000005b048210610a785760006000fd5b6013198160000b12158015610a91575060148160000b13155b1515610a9d5760006000fd5b60028160000b1215610ac657606254600090810b900b600303600a0a821015610ac65760006000fd5b5b6001829055600a825b0460025560628054600083810b60ff1660ff199092169190911790915560408051602090810183905281517f18160ddd0000000000000000000000000000000000000000000000000000000081529151600160a060020a038816926318160ddd92600480830193919282900301818787803b1515610b4a57fe5b6102c65a03f11515610b5857fe5b505060405151919091119050610b6e5760006000fd5b6000805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a0386811691909117825560408051602090810184905281517f18160ddd0000000000000000000000000000000000000000000000000000000081529151928716926318160ddd92600480820193929182900301818787803b1515610bf057fe5b6102c65a03f11515610bfe57fe5b505060405151919091119050610c145760006000fd5b6003805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a0385161790555b50505050565b3360008211610c525760006000fd5b600160a060020a038116600090815260066020526040902054821115610c785760006000fd5b600160a060020a038082166000818152600660209081526040808320805488900390556003548151830184905281517fa9059cbb000000000000000000000000000000000000000000000000000000008152600481019590955260248501889052905194169363a9059cbb93604480820194918390030190829087803b1515610cfd57fe5b6102c65a03f11515610d0b57fe5b50506040515115159050610d1f5760006000fd5b80600160a060020a03166000805160206130d08339815191526003600285600003604051808460038111156107bb57fe5b60ff1681526020018360028111156107cf57fe5b60ff168152602001828152602001935050505060405180910390a25b5050565b333460008111610d945760006000fd5b600160a060020a038216600081815260056020526040808220805485019055516000805160206130d08339815191529190600190859080846107bb565b60ff1681526020018360028111156107cf57fe5b60ff168152602001828152602001935050505060405180910390a25b5050565b60008054604080516020908101849052815160e160020a636eb1769f02815233600160a060020a038181166004840152308082166024850152945191969495169263dd62ed3e926044808201939182900301818787803b1515610e6457fe5b6102c65a03f11515610e7257fe5b50506040515191505060008111610e895760006000fd5b6000805460408051602090810184905281517f23b872dd000000000000000000000000000000000000000000000000000000008152600160a060020a038881166004830152878116602483015260448201879052925192909316936323b872dd9360648082019492918390030190829087803b1515610f0457fe5b6102c65a03f11515610f1257fe5b50506040515115159050610f265760006000fd5b60008054604080516020908101849052815160e160020a636eb1769f028152600160a060020a03888116600483015287811660248301529251929093169363dd62ed3e9360448082019492918390030190829087803b1515610f8457fe5b6102c65a03f11515610f9257fe5b505060405151159050610fa157fe5b600160a060020a038316600081815260046020526040808220805485019055516000805160206130d083398151915291600291859080845b60ff168152602001836002811115610fed57fe5b60ff168152602001828152602001935050505060405180910390a25b505050565b600160a060020a0380821660008181526004602081815260408084205460058352818520546006845282862054865484518601889052845160e160020a636eb1769f02815296870198909852308916602487015292519197909692959485948594859493169263dd62ed3e92604480820193929182900301818787803b151561109357fe5b6102c65a03f115156110a157fe5b505060408051805160035460006020938401819052845160e160020a636eb1769f028152600160a060020a038f8116600483015230811660248301529551939a5094909116945063dd62ed3e936044808201949392918390030190829087803b151561110957fe5b6102c65a03f1151561111757fe5b505060408051805160008054602093840182905284517f70a08231000000000000000000000000000000000000000000000000000000008152600160a060020a038f811660048301529551939950941694506370a08231936024808201949392918390030190829087803b151561118a57fe5b6102c65a03f1151561119857fe5b50506040805180516003546000602093840181905284517f70a08231000000000000000000000000000000000000000000000000000000008152600160a060020a038f8116600483015295519398509490911694506370a08231936024808201949392918390030190829087803b151561120e57fe5b6102c65a03f1151561121c57fe5b5050604051519150505b919395979092949650565b600754600160a060020a03908116903316811461124e5760006000fd5b600160a060020a0382811690821614156112685760006000fd5b6007805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a0384161790555b5050565b6003546040805160006020918201819052825160e160020a636eb1769f02815233600160a060020a03818116600484015230808216602485015295519196939493169263dd62ed3e926044808201939182900301818787803b15156112f657fe5b6102c65a03f1151561130457fe5b5050604051519150506000811161131b5760006000fd5b600354604080516000602091820181905282517f23b872dd000000000000000000000000000000000000000000000000000000008152600160a060020a038881166004830152878116602483015260448201879052935193909416936323b872dd936064808301949391928390030190829087803b151561139857fe5b6102c65a03f115156113a657fe5b505060405151151590506113ba5760006000fd5b6003546040805160006020918201819052825160e160020a636eb1769f028152600160a060020a03888116600483015287811660248301529351939094169363dd62ed3e936044808301949391928390030190829087803b151561141a57fe5b6102c65a03f1151561142857fe5b50506040515115905061143757fe5b600160a060020a03831660008181526006602052604090819020805484019055516000805160206130d083398151915290600290819085908083610fd9565b60ff168152602001836002811115610fed57fe5b60ff168152602001828152602001935050505060405180910390a25b505050565b6000808080808080808080806001608060020a038d1615156114f057600160a060020a038e166000908152606060205260409020546001608060020a03169a5061150e565b6001608060020a03808e16600090815260616020526040902054169a505b5b6001608060020a038b16151561152457611621565b506001608060020a03808b166000818152600860205260409020918d16901061154c576115b8565b60025b6002820154610100900460ff16600781111561156757fe5b148061158b575060045b6002820154610100900460ff16600781111561158957fe5b145b15611595576115b8565b6001608060020a039a8b16600090815260616020526040902054909a169961150e565b8054600182015460028301546003840154600485015460a060020a90940461ffff169d50919b5060ff8082169b50610100820481169a50620100008204169850630100000090046001608060020a0390811697508082169650608060020a909104811694501691505b5093979b5093979b91959950939750565b33600080808080806001608060020a038c161580159061167157506001608060020a038c16600090815260086020526040902054600160a060020a0316155b151561167d5760006000fd5b86600160a060020a03167f98f28571fb2c7cab95fb6ca13908a1ba4a08e2b417317f72037ea2fafd2bca4760008e8b604051808460028111156116bc57fe5b60ff1681526001608060020a0390931660208401525060408083019190915251908190036060019150a26101406040519081016040528088600160a060020a031681526020018c61ffff1681526020018b81526020018a600381111561171e57fe5b815260200160005b815260200160005b815260200160006001608060020a0316815260200160006001608060020a0316815260200160006001608060020a0316815260200160006001608060020a0316815250600860008e6001608060020a03166001608060020a0316815260200190815260200160002060008201518160000160006101000a815481600160a060020a030219169083600160a060020a0316021790555060208201518160000160146101000a81548161ffff021916908361ffff1602179055506040820151816001015560608201518160020160006101000a81548160ff0219169083600381111561181457fe5b0217905550608082015160028201805461ff00191661010083600781111561183857fe5b021790555060a082015160028201805462ff000019166201000083600881111561185e57fe5b021790555060c08201518160020160036101000a8154816001608060020a0302191690836001608060020a0316021790555060e08201518160030160006101000a8154816001608060020a0302191690836001608060020a031602179055506101008201518160030160106101000a8154816001608060020a0302191690836001608060020a031602179055506101208201518160040160006101000a8154816001608060020a0302191690836001608060020a031602179055509050506060600088600160a060020a0316600160a060020a0316815260200190815260200160002060009054906101000a90046001608060020a031695508b6060600089600160a060020a0316600160a060020a0316815260200190815260200160002060006101000a8154816001608060020a0302191690836001608060020a0316021790555085606160008e6001608060020a03166001608060020a0316815260200190815260200160002060006101000a8154816001608060020a0302191690836001608060020a03160217905550600860008d6001608060020a03166001608060020a031681526020019081526020016000209450611a1b8b611f0d565b9195509350915060005b846002811115611a3157fe5b1415611a70576002850180546001919061ff001916610100835b02179055506002850180546001919062ff0000191662010000835b0217905550611bf2565b6001548a1080611a8c57506c0c9f2c9cd04674edea400000008a115b15611acb576002850180546001919061ff001916610100835b02179055506002858101805462ff000019166201000083611a66565b0217905550611bf2565b611ad68a8484611f94565b9050662386f26fc10000811080611af957506c0c9f2c9cd04674edea4000000081115b15611b3c576002850180546001919061ff00191661010083611aa5565b02179055506002858101805462ff000019166201000083611a66565b0217905550611bf2565b60035b896003811115611b4b57fe5b148015611b5757508715155b15611b99576002850180546001919061ff001916610100835b02179055506002850180546003919062ff000019166201000083611a66565b0217905550611bf2565b611ba587858c84611fe9565b1515611be8576002850180546001919061ff001916610100835b02179055506002850180546004919062ff000019166201000083611a66565b0217905550611bf2565b611bf28c896120cb565b5b505050505050505050505050565b6001608060020a03821660009081526008602052604090208054339190600160a060020a03808416911614611c365760006000fd5b60045b6002820154610100900460ff166007811115611c5157fe5b14611c5b57610c3d565b81600160a060020a03167f98f28571fb2c7cab95fb6ca13908a1ba4a08e2b417317f72037ea2fafd2bca476001868660405180846002811115611c9a57fe5b60ff1681526001608060020a0390931660208401525060408083019190915251908190036060019150a26002810180546000919061ff001916610100835b0217905550610c3d84846120cb565b5b50505050565b6001608060020a03811660009081526008602052604081208054339290600160a060020a03808516911614611d235760006000fd5b50600281810154610100900460ff16905b816007811115611d4057fe5b14158015611d5b575060045b816007811115611d5857fe5b14155b15611d6557610c3d565b82600160a060020a03167f98f28571fb2c7cab95fb6ca13908a1ba4a08e2b417317f72037ea2fafd2bca47600286600060405180846002811115611da557fe5b60ff1681526001608060020a0390931660208401525060408083019190915251908190036060019150a260025b816007811115611dde57fe5b1415611e6857611ded846123c0565b815460028301546001808501546040516001608060020a038981169542956000805160206130f0833981519152959460a060020a90920461ffff1693630100000090910490921690039060009080855b60ff16815261ffff909416602085015250604080840192909252606083015251908190036080019150a35b610c3d8460036008612540565b5b50505050565b600060018261ffff1610158015611e995750612a308261ffff1611155b90505b919050565b6000600060005b506001608060020a0392831660009081526008602090815260408083206002810154600180830154605f9095529290942054871696630100000090940490931690910393909301929190910190831515611f0157611f06565b611ea8565b5b50915091565b6000808061ffff84168180806001841080611f29575061546084115b15611f3f57600096506000955060009450611f89565b612a308411611f58576001965083612a30039250611f64565b60029650612a31840392505b5b610384835b069150610384835b60625460648501985060000b919004908101955090505b505050509193909250565b600060008260000b1215611fc9576000828103900b600a0a6103e861ffff851686025b04811515611fc157fe5b049050611fe1565b600082900b600a0a6103e861ffff851686025b040290505b5b9392505050565b6000808060015b866002811115611ffc57fe5b141561205457600160a060020a03871660009081526005602052604090205491508382101561202e57600092506120bf565b600160a060020a03871660009081526005602052604090208483039055600192506120bf565b60025b86600281111561206357fe5b14156120ba5750600160a060020a0386166000908152600460205260409020548481101561209457600092506120bf565b600160a060020a03871660009081526004602052604090208582039055600192506120bf565b600092505b5b5b5050949350505050565b6001608060020a0382811660009081526008602052604081206002810154600382015482549294630100000090920482169391169181908190819061211a9060a060020a900461ffff16611f0d565b50909450600190505b84600281111561212f57fe5b1461213b57600161213f565b612a315b87549093506121589060a060020a900461ffff1661264c565b61ffff16915061216a8984848b6126ab565b90506121778987876128de565b60025b600288015460ff16600381111561218d57fe5b141561220f5760025b8160048111156121a257fe5b14156121ba576121b58960036000612540565b6123b4565b60015b8160048111156121c957fe5b14156121e1576121b58960036007612540565b6123b4565b60045b8160048111156121f057fe5b1415612208576121b58960036006612540565b6123b4565b5b5b6100ee565b60035b600288015460ff16600381111561222557fe5b141561227b5760015b81600481111561223a57fe5b1415612252576121b58960016005612540565b6123b4565b60045b81600481111561226157fe5b1415612208576121b589612afa565b6123b4565b5b6100ee565b60005b600288015460ff16600381111561229157fe5b14156123135760025b8160048111156122a657fe5b14156122be576121b58960036000612540565b6123b4565b60015b8160048111156122cd57fe5b1415612252576121b58960036007612540565b6123b4565b6004612255565b81600481111561226157fe5b1415612208576121b589612afa565b6123b4565b5b5b6100ee565b60015b600288015460ff16600381111561232957fe5b14156100ee5760025b81600481111561233e57fe5b1415612356576121b58960036000612540565b6123b4565b60015b81600481111561236557fe5b1415612389576002870180546004919061ff001916610100835b02179055506123b4565b60045b81600481111561239857fe5b14156100ee576121b589612afa565b6123b4565b5b5b5b5b5b5bfe5b5b505050505050505050565b6001608060020a038181166000818152600860209081526040808320805460a060020a900461ffff16808552605e8452828520958552605f90935290832080549195929493909282811692608060020a900416908080808515612450576001608060020a038087166000908152605f602052604090208054878316608060020a0292169190911781559350612469565b87546001608060020a03808716608060020a0291161788555b6001608060020a038516156124ad576001608060020a038581166000908152605f6020526040902080546001608060020a03191691881691909117815592506124c7565b87546001608060020a0319166001608060020a0387161788555b6001608060020a0386161580156124e557506001608060020a038516155b156125325761010061ffff8a165b0461ffff1691506101008961ffff1681151561250b57fe5b0661ffff1690508060020a60098360558110151561252557fe5b0160005b50805490911890555b5b5050505050505050505050565b6001608060020a03831660009081526008602052604081208054909160a060020a90910461ffff169061257282611e7c565b156125be57612585836001015483612ca3565b60038401548454600160a060020a0316600090815260056020526040902080546001608060020a039092168303909101905590506125fd565b600283015460018401548454600160a060020a03166000908152600460205260409020805463010000009093046001608060020a031690910390910190555b60028301805486919061ff00191661010083600781111561261a57fe5b021790555060028301805485919062ff000019166201000083600881111561263e57fe5b02179055505b505050505050565b600060018261ffff16108061266657506154608261ffff16115b1561267357506000611e9c565b612a3061ffff831611612692575061ffff811661546003600101611e9c565b50600161ffff82166154600301611e9c565b5b5b919050565b6001608060020a038416600090815260086020526040812081808080808080806101008d5b0497506101008d5b0696506101008c5b0495506101008c5b069450600988605581106126f857fe5b0160005b505460009a509350839250600287900a830491505b858810156127ef57811580612727575086610100145b156127725782841461274857826009896055811061274157fe5b0160005b50555b600190970196600096506009886055811061275f57fe5b0160005b505493508391508392506127ea565b60018216156127d95761278c89888a61010002018d612ccd565b909c509a50905080156127a2578660020a831892505b60035b8a60048111156127b157fe5b14156127c057600099506127d9565b60005b8a60048111156127cf57fe5b146127d9576127ef565b5b5b600196909601956002825b0491505b612711565b60005b8a60048111156127fe57fe5b1415612895575b84871115801561281457508115155b156128955760018216156128805761283389888a61010002018d612ccd565b909c509a5090508015612849578660020a831892505b60035b8a600481111561285857fe5b14156128675760009950612880565b60005b8a600481111561287657fe5b1461288057612895565b5b5b600196909601956002825b049150612805565b5b8284146128b25782600989605581106128ab57fe5b0160005b50555b60005b8a60048111156128c157fe5b14156128cc57600499505b5b505050505050505050949350505050565b6001608060020a03808416600090815260086020526040812060028101546003820154919363010000009091048116869003929116849003908080806103e86107d0865b8954600160a060020a0316600081815260066020526040902054929091049290920295509092509050808411612a0357600160a060020a0380831660009081526006602052604080822087850390556007549092168152208490556004870180546001608060020a038082168701166001608060020a031990911617905586546129b79061ffff60a060020a90910416611e7c565b156129df57600160a060020a03821660009081526004602052604090208054870190556129fe565b600160a060020a03821660009081526005602052604090208054860190555b612aec565b8654612a199060a060020a900461ffff16611e7c565b15612a87576107d0865b8854600160a060020a03908116600090815260046020526040808220805495909404808c039590950190935560038b0180546001608060020a03608060020a8083048216880182160291161790556007549091168152208054820190559250612aec565b6107d0855b8854600160a060020a03908116600090815260056020526040808220805495909404808b039590950190935560038b0180546001608060020a03608060020a80830482168801821602911617905560075490911681522080548201905592505b5b5b50505050505050505050565b6001608060020a038181166000818152600860209081526040808320805460a060020a900461ffff16808552605e8452828520958552605f9093529083208454919592949390929091829182918291161515612bc75785546001608060020a0319166001608060020a038a811691821716608060020a919091021786556000855561010061ffff88165b0461ffff1693506101008761ffff16811515612b9c57fe5b0661ffff1692508260020a600985605581101515612bb657fe5b0160005b5080549091179055612c16565b505083546001608060020a03608060020a9182900481166000818152605f60205260409020818402875580548b84166001608060020a0319919091168117825588549402939092169290921786555b600288015460018901546040516001608060020a03808d169342936000805160206130f0833981519152936000938e93630100000090041690910390839080825b60ff16815261ffff909416602085015250604080840192909252606083015251908190036080019150a36002888101805461ff001916610100835b02179055505b505050505050505050565b600060006000612cb284611f0d565b9250925050612cc2858383611f94565b92505b505092915050565b6002830154600384015461ffff84166000908152605e60205260408120549092849284926001608060020a036301000000909304831692918216911683805b861515612d1c5760019550612d63565b612d2d858c6001015403848c612e2a565b600019909a0199985096810196958601959194509250905060005b866004811115612d5457fe5b14612d5e57612d63565b612d0c565b60028b01805472ffffffffffffffffffffffffffffffff000000191663010000006001608060020a03888116919091029190911790915560038c0180546001608060020a03191686831617905583161515612dd55761ffff8a166000908152605e602052604081205560019750612e1b565b61ffff8a166000908152605e6020908152604080832080546001608060020a0319166001608060020a038881169182179092558452605f90925282208054909116905597505b5b505050505093509350939050565b6001608060020a0380831660009081526008602052604081206002810154600182015492938493849384939092630100000090910416900382818a1015612e7357899550612e77565b8195505b612e818689612ca3565b9450600254868b031015612e985760029350612e9d565b600093505b612eaa838a8a8989612f12565b90508015612f01576001608060020a03808a166000908152605f602052604081205490911697505b846004811115612ede57fe5b148015612ef257506001608060020a038716155b15612efc57600393505b612f05565b8896505b5b50505093509350935093565b60028501805472ffffffffffffffffffffffffffffffff0000001981166301000000918290046001608060020a0390811686018116909202179091556003860180546001608060020a031981169083168401909216919091179055600080612f7985611e7c565b15612fa2578654600160a060020a03166000908152600460205260409020805485019055612fc2565b8654600160a060020a031660009081526005602052604090208054840190555b8660020160039054906101000a90046001608060020a03166001608060020a03168760010154039050600254811015613066576130028660036000612540565b856001608060020a0316426000805160206130f0833981519152600288858901896040518085600381111561303357fe5b60ff16815261ffff909416602085015250604080840192909252606083015251908190036080019150a3600191506130c4565b856001608060020a0316426000805160206130f083398151915260038888896040518085600381111561309557fe5b60ff16815261ffff909416602085015250604080840192909252606083015251908190036080019150a3600091505b5b50959450505050505600caa7be131f5091d6ed32c02b2ba1f45c6721442175b1c8e1a9a5b453e54efe7540d0a103f9440846844f8f9a0d6968bb70b1a10f7ce225d40eb8796c4993258da165627a7a723058201b30f80ed00802b1385ff591272852defaea43bd8763e3ddf874557c67b2fb9d0029

Swarm Source

bzzr://1b30f80ed00802b1385ff591272852defaea43bd8763e3ddf874557c67b2fb9d

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading
[ Download: CSV Export  ]
[ Download: CSV Export  ]

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