Home Reference Source

lib/data/decode/utils.js

  1. import debugModule from "debug";
  2. const debug = debugModule("debugger:data:decode:utils");
  3.  
  4. import { BigNumber } from "bignumber.js";
  5. import Web3 from "web3";
  6.  
  7. export const WORD_SIZE = 0x20;
  8. export const MAX_WORD = new BigNumber(2).pow(256).minus(1);
  9.  
  10. /**
  11. * recursively converts big numbers into something nicer to look at
  12. */
  13. export function cleanBigNumbers(value) {
  14. if (BigNumber.isBigNumber(value)) {
  15. return value.toNumber();
  16.  
  17. } else if (value && value.map != undefined) {
  18. return value.map( (inner) => cleanBigNumbers(inner) );
  19.  
  20. } else if (value && typeof value == "object") {
  21. return Object.assign(
  22. {}, ...Object.entries(value)
  23. .map( ([key, inner]) => ({ [key]: cleanBigNumbers(inner) }) )
  24. );
  25.  
  26. } else {
  27. return value;
  28. }
  29. }
  30.  
  31. export function typeIdentifier(definition) {
  32. return definition.typeDescriptions.typeIdentifier;
  33. }
  34.  
  35. /**
  36. * returns basic type class for a variable definition node
  37. * e.g.:
  38. * `t_uint256` becomes `uint`
  39. * `t_struct$_Thing_$20_memory_ptr` becomes `struct`
  40. */
  41. export function typeClass(definition) {
  42. return typeIdentifier(definition).match(/t_([^$_0-9]+)/)[1];
  43. }
  44.  
  45. /**
  46. * Allocate storage for given variable declarations
  47. *
  48. * Postcondition: starts a new slot and occupies whole slots
  49. */
  50. export function allocateDeclarations(
  51. declarations,
  52. refs,
  53. slot = 0,
  54. index = WORD_SIZE - 1,
  55. path = []
  56. ) {
  57. if (index < WORD_SIZE - 1) { // starts a new slot
  58. slot++;
  59. index = WORD_SIZE - 1;
  60. }
  61.  
  62. let parentFrom = { slot, index: 0 };
  63. var parentTo = { slot, index: WORD_SIZE - 1 };
  64. let mapping = {};
  65.  
  66. for (let declaration of declarations) {
  67. let { from, to, next, children } =
  68. allocateDeclaration(declaration, refs, slot, index);
  69.  
  70. mapping[declaration.id] = { from, to, name: declaration.name };
  71. if (children !== undefined) {
  72. mapping[declaration.id].children = children;
  73. }
  74.  
  75. slot = next.slot;
  76. index = next.index;
  77.  
  78. parentTo = { slot: to.slot, index: WORD_SIZE - 1 };
  79. }
  80.  
  81. if (index < WORD_SIZE - 1) {
  82. slot++;
  83. index = WORD_SIZE - 1;
  84. }
  85.  
  86. return {
  87. from: parentFrom,
  88. to: parentTo,
  89. next: { slot, index },
  90. children: mapping
  91. };
  92. }
  93.  
  94. function allocateValue(slot, index, bytes) {
  95. let from = index - bytes + 1 >= 0 ?
  96. { slot, index: index - bytes + 1 } :
  97. { slot: slot + 1, index: WORD_SIZE - bytes };
  98.  
  99. let to = { slot: from.slot, index: from.index + bytes - 1 };
  100.  
  101. let next = from.index == 0 ?
  102. { slot: from.slot + 1, index: WORD_SIZE - 1 } :
  103. { slot: from.slot, index: from.index - 1 };
  104.  
  105. return { from, to, next };
  106. }
  107.  
  108. function allocateDeclaration(declaration, refs, slot, index) {
  109. let definition = refs[declaration.id].definition;
  110. var byteSize = storageSize(definition); // yum
  111.  
  112. if (typeClass(definition) != "struct") {
  113. return allocateValue(slot, index, byteSize);
  114. }
  115.  
  116. let struct = refs[definition.typeName.referencedDeclaration];
  117. debug("struct: %O", struct);
  118.  
  119. let result = allocateDeclarations(struct.variables || [], refs, slot, index);
  120. debug("struct result %o", result);
  121. return result;
  122. }
  123.  
  124. /**
  125. * e.g. uint48 -> 6
  126. * @return size in bytes for explicit type size, or `null` if not stated
  127. */
  128. export function specifiedSize(definition) {
  129. let specified = typeIdentifier(definition).match(/t_[a-z]+([0-9]+)/);
  130.  
  131. if (!specified) {
  132. return null;
  133. }
  134.  
  135. let num = specified[1];
  136.  
  137. switch (typeClass(definition)) {
  138. case "int":
  139. case "uint":
  140. return num / 8;
  141.  
  142. case "bytes":
  143. return num;
  144.  
  145. default:
  146. debug("Unknown type for size specification: %s", typeIdentifier(definition));
  147. }
  148. }
  149.  
  150. export function storageSize(definition) {
  151. switch (typeClass(definition)) {
  152. case "bool":
  153. return 1;
  154.  
  155. case "address":
  156. return 20;
  157.  
  158. case "int":
  159. case "uint":
  160. // is this a HACK? ("256" / 8)
  161. return typeIdentifier(definition).match(/t_[a-z]+([0-9]+)/)[1] / 8;
  162.  
  163. case "string":
  164. case "bytes":
  165. case "array":
  166. return WORD_SIZE;
  167. }
  168. }
  169.  
  170. export function isReference(definition) {
  171. return typeIdentifier(definition).match(/_(memory|storage)(_ptr)?$/) != null;
  172. }
  173.  
  174. export function referenceType(definition) {
  175. return typeIdentifier(definition).match(/_([^_]+)(_ptr)?$/)[1];
  176. }
  177.  
  178. export function baseDefinition(definition) {
  179. let baseIdentifier = typeIdentifier(definition)
  180. // first dollar sign last dollar sign
  181. // `---------. ,---'
  182. .match(/^[^$]+\$_(.+)_\$[^$]+$/)[1]
  183. // `----' greedy match
  184.  
  185. // HACK - internal types for memory or storage also seem to be pointers
  186. if (baseIdentifier.match(/_(memory|storage)$/) != null) {
  187. baseIdentifier = `${baseIdentifier}_ptr`;
  188. }
  189.  
  190. // another HACK - we get away with it becausewe're only using that one property
  191. return {
  192. typeDescriptions: {
  193. typeIdentifier: baseIdentifier
  194. }
  195. };
  196. }
  197.  
  198.  
  199. export function toBigNumber(bytes) {
  200. if (bytes == undefined) {
  201. return undefined;
  202. } else if (typeof bytes == "string") {
  203. return new BigNumber(bytes, 16);
  204. } else if (typeof bytes == "number" || BigNumber.isBigNumber(bytes)) {
  205. return new BigNumber(bytes);
  206. } else if (bytes.reduce) {
  207. return bytes.reduce(
  208. (num, byte) => num.times(0x100).plus(byte),
  209. new BigNumber(0)
  210. );
  211. }
  212. }
  213.  
  214. export function toSignedBigNumber(bytes) {
  215. if (bytes[0] < 0b10000000) { // first bit is 0
  216. return toBigNumber(bytes);
  217. } else {
  218. return toBigNumber(bytes.map( (b) => 0xff - b )).plus(1).negated();
  219. }
  220. }
  221.  
  222. /**
  223. * @param bytes - Uint8Array
  224. * @param length - desired byte length (pad with zeroes)
  225. * @param trim - omit leading zeroes
  226. */
  227. export function toHexString(bytes, length = 0, trim = false) {
  228. if (typeof length == "boolean") {
  229. trim = length;
  230. length = 0;
  231. }
  232.  
  233. if (BigNumber.isBigNumber(bytes)) {
  234. bytes = toBytes(bytes);
  235. }
  236.  
  237. const pad = (s) => `${"00".slice(0, 2 - s.length)}${s}`
  238.  
  239. // 0 1 2 3 4
  240. // 0 1 2 3 4 5 6 7
  241. // bytes.length: 5 - 0x( e5 c2 aa 09 11 )
  242. // length (preferred): 8 - 0x( 00 00 00 e5 c2 aa 09 11 )
  243. // `--.---'
  244. // offset 3
  245. if (bytes.length < length) {
  246. let prior = bytes;
  247. bytes = new Uint8Array(length);
  248.  
  249. bytes.set(prior, length - prior.length);
  250. }
  251.  
  252. debug("bytes: %o", bytes);
  253.  
  254. let string = bytes.reduce(
  255. (str, byte) => `${str}${pad(byte.toString(16))}`, ""
  256. );
  257.  
  258. if (trim) {
  259. string = string.replace(/^(00)+/, "");
  260. }
  261.  
  262. if (string.length == 0) {
  263. string = "00";
  264. }
  265.  
  266. return `0x${string}`;
  267. }
  268.  
  269. export function toBytes(number, length = 0) {
  270. if (number < 0) {
  271. return [];
  272. }
  273.  
  274. let hex = number.toString(16);
  275. if (hex.length % 2 == 1) {
  276. hex = `0${hex}`;
  277. }
  278.  
  279. let bytes = new Uint8Array(
  280. hex.match(/.{2}/g)
  281. .map( (byte) => parseInt(byte, 16) )
  282. );
  283.  
  284. if (bytes.length < length) {
  285. let prior = bytes;
  286. bytes = new Uint8Array(length);
  287. bytes.set(prior, length - prior.length);
  288. }
  289.  
  290. return bytes;
  291. }
  292.  
  293. export function keccak256(...args) {
  294. let web3 = new Web3();
  295.  
  296. args = args.map( (arg) => {
  297. if (typeof arg == "number" || BigNumber.isBigNumber(arg)) {
  298. return toHexString(toBytes(arg, WORD_SIZE)).slice(2)
  299. } else if (typeof arg == "string") {
  300. return web3.toHex(arg).slice(2);
  301. } else {
  302. return "";
  303. }
  304. });
  305.  
  306. let sha = web3.sha3(args.join(''), { encoding: 'hex' });
  307. debug("sha %o", sha);
  308. return toBigNumber(sha);
  309. }