@truffle/codec
Version:
Library for encoding and decoding smart contract data
426 lines • 17.6 kB
JavaScript
"use strict";
/**
* @protected
*
* @packageDocumentation
*/
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.decodeStorageReference = exports.decodeStorageReferenceByAddress = exports.decodeStorage = void 0;
const debug_1 = __importDefault(require("debug"));
const debug = (0, debug_1.default)("codec:storage:decode");
const read_1 = __importDefault(require("../../read"));
const Conversion = __importStar(require("../../conversion"));
const Format = __importStar(require("../../format"));
const Basic = __importStar(require("../../basic"));
const Bytes = __importStar(require("../../bytes"));
const Utils = __importStar(require("../utils"));
const Evm = __importStar(require("../../evm"));
const allocate_1 = require("../allocate");
const bn_js_1 = __importDefault(require("bn.js"));
const errors_1 = require("../../errors");
function* decodeStorage(dataType, pointer, info) {
if (Format.Types.isReferenceType(dataType)) {
return yield* decodeStorageReference(dataType, pointer, info);
}
else {
return yield* Basic.Decode.decodeBasic(dataType, pointer, info);
}
}
exports.decodeStorage = decodeStorage;
//decodes storage at the address *read* from the pointer -- hence why this takes DataPointer rather than StoragePointer.
//NOTE: ONLY for use with pointers to reference types!
//Of course, pointers to value types don't exist in Solidity, so that warning is redundant, but...
function* decodeStorageReferenceByAddress(dataType, pointer, info) {
const allocations = info.allocations.storage;
let rawValue;
try {
rawValue = yield* (0, read_1.default)(pointer, info.state);
}
catch (error) {
return (0, errors_1.handleDecodingError)(dataType, error);
}
const startOffset = Conversion.toBN(rawValue);
let rawSize;
try {
rawSize = (0, allocate_1.storageSize)(dataType, info.userDefinedTypes, allocations, info.currentContext.compiler);
}
catch (error) {
return (0, errors_1.handleDecodingError)(dataType, error);
}
//we *know* the type being decoded must be sized in words, because it's a
//reference type, but TypeScript doesn't, so we'll have to use a type
//coercion
const size = rawSize.words;
//now, construct the storage pointer
const newPointer = {
location: "storage",
range: {
from: {
slot: {
offset: startOffset
},
index: 0
},
to: {
slot: {
offset: startOffset.addn(size - 1)
},
index: Evm.Utils.WORD_SIZE - 1
}
}
};
//dispatch to decodeStorageReference
return yield* decodeStorageReference(dataType, newPointer, info);
}
exports.decodeStorageReferenceByAddress = decodeStorageReferenceByAddress;
function* decodeStorageReference(dataType, pointer, info) {
var data;
var length;
const { state } = info;
const allocations = info.allocations.storage;
switch (dataType.typeClass) {
case "array": {
debug("storage array! %o", pointer);
let lengthAsBN;
switch (dataType.kind) {
case "dynamic":
debug("dynamic array");
debug("type %O", dataType);
try {
data = yield* (0, read_1.default)(pointer, state);
}
catch (error) {
return (0, errors_1.handleDecodingError)(dataType, error);
}
lengthAsBN = Conversion.toBN(data);
break;
case "static":
debug("static array");
lengthAsBN = dataType.length;
break;
}
try {
length = lengthAsBN.toNumber();
}
catch (_a) {
return {
type: dataType,
kind: "error",
error: {
kind: "OverlongArraysAndStringsNotImplementedError",
lengthAsBN
}
};
}
debug("length %o", length);
debug("about to determine baseSize");
let baseSize;
try {
baseSize = (0, allocate_1.storageSize)(dataType.baseType, info.userDefinedTypes, allocations, info.currentContext.compiler);
}
catch (error) {
return (0, errors_1.handleDecodingError)(dataType, error);
}
debug("baseSize %o", baseSize);
//we are going to make a list of child ranges, pushing them one by one onto
//this list, and then decode them; the first part will vary based on whether
//we're in the words case or the bytes case, the second will not
let ranges = [];
if (Utils.isWordsLength(baseSize)) {
//currentSlot will point to the start of the entry being decoded
let currentSlot = {
path: pointer.range.from.slot,
offset: new bn_js_1.default(0),
hashPath: dataType.kind === "dynamic"
};
for (let i = 0; i < length; i++) {
let childRange = {
from: {
slot: {
path: currentSlot.path,
offset: currentSlot.offset.clone(),
hashPath: currentSlot.hashPath
},
index: 0
},
to: {
slot: {
path: currentSlot.path,
offset: currentSlot.offset.addn(baseSize.words - 1),
hashPath: currentSlot.hashPath
},
index: Evm.Utils.WORD_SIZE - 1
}
};
ranges.push(childRange);
currentSlot.offset.iaddn(baseSize.words);
}
}
else {
const perWord = Math.floor(Evm.Utils.WORD_SIZE / baseSize.bytes);
debug("perWord %d", perWord);
//currentPosition will point to the start of the entry being decoded
//note we have baseSize.bytes <= Evm.Utils.WORD_SIZE
let currentPosition = {
slot: {
path: pointer.range.from.slot,
offset: new bn_js_1.default(0),
hashPath: dataType.kind === "dynamic"
},
index: Evm.Utils.WORD_SIZE - baseSize.bytes //note the starting index!
};
for (let i = 0; i < length; i++) {
let childRange = {
from: {
slot: {
path: currentPosition.slot.path,
offset: currentPosition.slot.offset.clone(),
hashPath: currentPosition.slot.hashPath
},
index: currentPosition.index
},
length: baseSize.bytes
};
ranges.push(childRange);
currentPosition.index -= baseSize.bytes;
if (currentPosition.index < 0) {
currentPosition.slot.offset.iaddn(1);
currentPosition.index = Evm.Utils.WORD_SIZE - baseSize.bytes;
}
}
}
let decodedChildren = [];
for (let childRange of ranges) {
decodedChildren.push(yield* decodeStorage(dataType.baseType, { location: "storage", range: childRange }, info));
}
return {
type: dataType,
kind: "value",
value: decodedChildren,
interpretations: {}
};
}
case "bytes":
case "string": {
try {
data = yield* (0, read_1.default)(pointer, state);
}
catch (error) {
return (0, errors_1.handleDecodingError)(dataType, error);
}
let lengthByte = data[Evm.Utils.WORD_SIZE - 1];
if (lengthByte % 2 == 0) {
// string lives in word, length is last byte / 2
length = lengthByte / 2;
debug("in-word; length %o", length);
return yield* Bytes.Decode.decodeBytes(dataType, {
location: "storage",
range: {
from: { slot: pointer.range.from.slot, index: 0 },
to: { slot: pointer.range.from.slot, index: length - 1 }
}
}, info);
}
else {
let lengthAsBN = Conversion.toBN(data).subn(1).divn(2);
try {
length = lengthAsBN.toNumber();
}
catch (_b) {
return {
//again with the TS failures...
type: dataType,
kind: "error",
error: {
kind: "OverlongArraysAndStringsNotImplementedError",
lengthAsBN
}
};
}
debug("new-word, length %o", length);
return yield* Bytes.Decode.decodeBytes(dataType, {
location: "storage",
range: {
from: {
slot: {
path: pointer.range.from.slot,
offset: new bn_js_1.default(0),
hashPath: true
},
index: 0
},
length
}
}, info);
}
}
case "struct": {
const typeId = dataType.id;
const structAllocation = allocations[typeId];
if (!structAllocation) {
return {
type: dataType,
kind: "error",
error: {
kind: "UserDefinedTypeNotFoundError",
type: dataType
}
};
}
let decodedMembers = [];
const members = structAllocation.members;
for (let index = 0; index < members.length; index++) {
const memberAllocation = members[index];
const memberPointer = memberAllocation.pointer;
//the type system thinks memberPointer might also be a constant
//definition pointer. However, structs can't contain constants,
//so *we* know it's not, and can safely coerce it.
debug("pointer %O", pointer);
const childRange = {
from: {
slot: {
path: pointer.range.from.slot,
offset: memberPointer.range.from.slot.offset.clone()
//note that memberPointer should have no path
},
index: memberPointer.range.from.index
},
to: {
slot: {
path: pointer.range.from.slot,
offset: memberPointer.range.to.slot.offset.clone()
//note that memberPointer should have no path
},
index: memberPointer.range.to.index
}
};
let storedType = info.userDefinedTypes[typeId];
if (!storedType) {
return {
type: dataType,
kind: "error",
error: {
kind: "UserDefinedTypeNotFoundError",
type: dataType
}
};
}
let storedMemberType = storedType.memberTypes[index].type;
let memberType = Format.Types.specifyLocation(storedMemberType, "storage");
decodedMembers.push({
name: memberAllocation.name,
value: yield* decodeStorage(memberType, { location: "storage", range: childRange }, info)
});
}
return {
type: dataType,
kind: "value",
value: decodedMembers,
interpretations: {}
};
}
case "mapping": {
debug("decoding mapping");
const valueType = dataType.valueType;
let valueSize;
try {
valueSize = (0, allocate_1.storageSize)(valueType, info.userDefinedTypes, allocations, info.currentContext.compiler);
}
catch (error) {
return (0, errors_1.handleDecodingError)(dataType, error);
}
let decodedEntries = [];
const baseSlot = pointer.range.from.slot;
debug("baseSlot %o", baseSlot);
debug("base slot address %o", Utils.slotAddress(baseSlot));
const keySlots = info.mappingKeys.filter(({ path }) => Utils.slotAddress(baseSlot).eq(Utils.slotAddress(path)));
for (const { key } of keySlots) {
let valuePointer;
if (Utils.isWordsLength(valueSize)) {
valuePointer = {
location: "storage",
range: {
from: {
slot: {
key,
path: baseSlot,
offset: new bn_js_1.default(0)
},
index: 0
},
to: {
slot: {
key,
path: baseSlot,
offset: new bn_js_1.default(valueSize.words - 1)
},
index: Evm.Utils.WORD_SIZE - 1
}
}
};
}
else {
valuePointer = {
location: "storage",
range: {
from: {
slot: {
key,
path: baseSlot,
offset: new bn_js_1.default(0)
},
index: Evm.Utils.WORD_SIZE - valueSize.bytes
},
to: {
slot: {
key,
path: baseSlot,
offset: new bn_js_1.default(0)
},
index: Evm.Utils.WORD_SIZE - 1
}
}
};
}
decodedEntries.push({
key,
value: yield* decodeStorage(valueType, valuePointer, info)
});
}
return {
type: dataType,
kind: "value",
value: decodedEntries,
interpretations: {}
};
}
}
}
exports.decodeStorageReference = decodeStorageReference;
//# sourceMappingURL=index.js.map