UNPKG

@fleupold/dex-contracts

Version:

Contracts for dFusion multi-token batch auction exchange

143 lines (142 loc) 4.92 kB
/** * encoding.js * * This NPM module provide encoding and decoding utilities for interacting with * dFusion smart contracts where manual byte encoding was needed because of * solidity ABI limitations. */ import BN from "bn.js"; import assert from "assert"; class OrderBuffer { constructor(bytes) { this.bytes = bytes; this.index = 2; // skip '0x' this.readBytes = (size) => this.bytes.slice(this.index, (this.index += size * 2)); this.decodeAddr = () => `0x${this.readBytes(20)}`; this.decodeInt = (size) => new BN(this.readBytes(size / 8), 16); this.decodeNumber = (size) => parseInt(this.readBytes(size / 8), 16); this.hasMoreBytes = () => this.index < this.bytes.length; } } function decodeOrder(bytes) { return { user: bytes.decodeAddr(), sellTokenBalance: bytes.decodeInt(256), buyToken: bytes.decodeNumber(16), sellToken: bytes.decodeNumber(16), validFrom: bytes.decodeNumber(32), validUntil: bytes.decodeNumber(32), priceNumerator: bytes.decodeInt(128), priceDenominator: bytes.decodeInt(128), remainingAmount: bytes.decodeInt(128), }; } function decodeIndexedOrder(bytes) { return { ...decodeOrder(bytes), orderId: bytes.decodeNumber(16), }; } function decodeOrdersInternal(bytes, decodeFunction, width) { if (bytes === null || bytes === undefined || bytes.length === 0) { return []; } assert((bytes.length - 2) % width === 0, "malformed bytes"); const buffer = new OrderBuffer(bytes); const result = []; while (buffer.hasMoreBytes()) { result.push(decodeFunction(buffer)); } return result; } /** * Decodes a byte-encoded variable length array of orders. This can be used to * decode the result of `BatchExchange.getEncodedUserOrders` and * `BatchExchange.getEncodedOrders`. */ export function decodeOrders(bytes) { return decodeOrdersInternal(bytes, decodeOrder, 112); } /** * Decodes a byte-encoded variable length array of orders and their indices. * This can be used to decode the result of `BatchExchangeViewer.getOpenOrderBook` and * `BatchExchangeViewer.getFinalizedOrderBook`. */ export function decodeIndexedOrders(bytes) { return decodeOrdersInternal(bytes, decodeIndexedOrder, 114); } class OrderEncoder { constructor(bytes) { this.bytes = bytes; this.index = 0; this.view = new DataView(bytes.buffer); } encodeHex(hex) { const len = hex.length / 2; for (let i = 0; i < len; i++) { this.bytes[i + this.index] = parseInt(hex.substr(i * 2, 2), 16); } this.index += len; } encodeAddr(addr) { assert(addr.length === 42 && addr.substr(0, 2) === "0x", `invalid Ethereum address '${addr}`); this.encodeHex(addr.substr(2)); } encodeInt(size, value) { const hex = typeof value === "string" ? BigInt(value).toString(16) : value.toString(16); assert(hex.length < size * 2, `value ${value} overflows ${size} bits`); this.encodeHex(hex.padStart(size / 4, "0")); } encodeNumber(size, value) { let setter; switch (size) { case 16: setter = "setUint16"; break; case 32: setter = "setUint32"; break; } this.view[setter](this.index, value, false); this.index += size / 8; } encodeOrder(order) { this.encodeAddr(order.user); this.encodeInt(256, order.sellTokenBalance); this.encodeNumber(16, order.buyToken); this.encodeNumber(16, order.sellToken); this.encodeNumber(32, order.validFrom); this.encodeNumber(32, order.validUntil); this.encodeInt(128, order.priceNumerator); this.encodeInt(128, order.priceDenominator); this.encodeInt(128, order.remainingAmount); } encodeIndexedOrder(order) { this.encodeOrder(order); this.encodeNumber(16, order.orderId); } } function encodeOrdersInternal(orders, stride, encode) { const bytes = new Uint8Array(orders.length * stride); const encoder = new OrderEncoder(bytes); for (const order of orders) { encode(encoder, order); } return bytes; } /** * Encodes an array of orders into a `Uint8Array` of bytes. This uses the same * format as `BatchExchange.getEncodedOrders`. */ export function encodeOrders(orders) { return encodeOrdersInternal(orders, 112, (encoder, order) => encoder.encodeOrder(order)); } /** * Encodes an array of indexed orders into a `Uint8Array` of bytes. This uses * the same format as `BatchExchangeViewer.getFilteredOrderBook`. */ export function encodeIndexedOrders(orders) { return encodeOrdersInternal(orders, 114, (encoder, order) => encoder.encodeIndexedOrder(order)); }