@fleupold/dex-contracts
Version:
Contracts for dFusion multi-token batch auction exchange
143 lines (142 loc) • 4.92 kB
JavaScript
/**
* 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));
}