@truffle/codec
Version:
Library for encoding and decoding smart contract data
193 lines • 8.21 kB
JavaScript
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.encodeTupleAbiWithSelector = exports.encodeTupleAbi = exports.encodeAbi = void 0;
const debug_1 = __importDefault(require("debug"));
const debug = (0, debug_1.default)("codec:abi-data:encode");
const Conversion = __importStar(require("../../conversion"));
const Basic = __importStar(require("../../basic"));
const Bytes = __importStar(require("../../bytes"));
const Evm = __importStar(require("../../evm"));
const allocate_1 = require("../allocate");
const sum_1 = __importDefault(require("lodash/sum"));
//UGH -- it turns out TypeScript can't handle nested tagged unions
//see: https://github.com/microsoft/TypeScript/issues/18758
//so, I'm just going to have to throw in a bunch of type coercions >_>
/**
* @Category Encoding (low-level)
*/
function encodeAbi(input, allocations) {
//errors can't be encoded
if (input.kind === "error") {
return undefined;
}
let bytes;
//TypeScript can at least infer in the rest of this that we're looking
//at a value, not an error! But that's hardly enough...
switch (input.type.typeClass) {
case "mapping":
case "magic":
case "type":
//none of these can go in the ABI
return undefined;
case "bytes":
switch (input.type.kind) {
case "static":
return Basic.Encode.encodeBasic(input);
case "dynamic":
bytes = Bytes.Encode.encodeBytes(input);
return padAndPrependLength(bytes);
}
case "string":
bytes = Bytes.Encode.encodeBytes(input);
return padAndPrependLength(bytes);
case "function": {
switch (input.type.visibility) {
case "internal":
return undefined; //internal functions can't go in the ABI!
//Yes, technically we could defer to encodeBasic here, but,
//c'mon, that's not how the function's supposed to be used
case "external":
return Basic.Encode.encodeBasic(input);
}
}
//now for the serious cases
case "array": {
let coercedInput = (input);
if (coercedInput.reference !== undefined) {
return undefined; //circular values can't be encoded
}
let staticEncoding = encodeTupleAbi(coercedInput.value, allocations);
switch (input.type.kind) {
case "static":
return staticEncoding;
case "dynamic":
let encoded = new Uint8Array(Evm.Utils.WORD_SIZE + staticEncoding.length); //leave room for length
encoded.set(staticEncoding, Evm.Utils.WORD_SIZE); //again, leave room for length beforehand
let lengthBytes = Conversion.toBytes(coercedInput.value.length, Evm.Utils.WORD_SIZE);
encoded.set(lengthBytes); //and now we set the length
return encoded;
}
}
case "struct": {
let coercedInput = (input);
if (coercedInput.reference !== undefined) {
return undefined; //circular values can't be encoded
}
return encodeTupleAbi(coercedInput.value.map(({ value }) => value), allocations);
}
case "tuple":
//WARNING: This case is written in a way that involves a bunch of unnecessary recomputation!
//(That may not be apparent from this one line, but it's true)
//I'm writing it this way anyway for simplicity, to avoid rewriting the encoder
//However it may be worth revisiting this in the future if performance turns out to be a problem
return encodeTupleAbi(input.value.map(({ value }) => value), allocations);
default:
return Basic.Encode.encodeBasic(input);
}
}
exports.encodeAbi = encodeAbi;
/**
* @Category Encoding (low-level)
*/
function padAndPrependLength(bytes) {
let length = bytes.length;
let paddedLength = Evm.Utils.WORD_SIZE * Math.ceil(length / Evm.Utils.WORD_SIZE);
let encoded = new Uint8Array(Evm.Utils.WORD_SIZE + paddedLength);
encoded.set(bytes, Evm.Utils.WORD_SIZE); //start 32 in to leave room for the length beforehand
let lengthBytes = Conversion.toBytes(length, Evm.Utils.WORD_SIZE);
encoded.set(lengthBytes); //and now we set the length
return encoded;
}
/**
* @Category Encoding (low-level)
*/
function encodeTupleAbi(tuple, allocations) {
let elementEncodings = tuple.map(element => encodeAbi(element, allocations));
if (elementEncodings.some(element => element === undefined)) {
return undefined;
}
let elementSizeInfo = tuple.map(element => (0, allocate_1.abiSizeInfo)(element.type, allocations));
//heads and tails here are as discussed in the ABI docs;
//for a static type the head is the encoding and the tail is empty,
//for a dynamic type the head is the pointer and the tail is the encoding
let heads = [];
let tails = [];
//but first, we need to figure out where the first tail will start,
//by adding up the sizes of all the heads (we can easily do this in
//advance via elementSizeInfo, without needing to know the particular
//values of the heads)
let startOfNextTail = (0, sum_1.default)(elementSizeInfo.map(elementInfo => elementInfo.size));
for (let i = 0; i < tuple.length; i++) {
let head;
let tail;
if (!elementSizeInfo[i].dynamic) {
//static case
head = elementEncodings[i];
tail = new Uint8Array(); //empty array
}
else {
//dynamic case
head = Conversion.toBytes(startOfNextTail, Evm.Utils.WORD_SIZE);
tail = elementEncodings[i];
}
heads.push(head);
tails.push(tail);
startOfNextTail += tail.length;
}
//finally, we need to concatenate everything together!
//since we're dealing with Uint8Arrays, we have to do this manually
let totalSize = startOfNextTail;
let encoded = new Uint8Array(totalSize);
let position = 0;
for (let head of heads) {
encoded.set(head, position);
position += head.length;
}
for (let tail of tails) {
encoded.set(tail, position);
position += tail.length;
}
return encoded;
}
exports.encodeTupleAbi = encodeTupleAbi;
/**
* @Category Encoding (low-level)
*/
function encodeTupleAbiWithSelector(tuple, selector, allocations) {
const encodedTuple = encodeTupleAbi(tuple, allocations);
if (!encodedTuple) {
return undefined;
}
const encoded = new Uint8Array(selector.length + encodedTuple.length);
encoded.set(selector);
encoded.set(encodedTuple, selector.length);
return encoded;
}
exports.encodeTupleAbiWithSelector = encodeTupleAbiWithSelector;
//# sourceMappingURL=index.js.map
;