Skip to content

Commit

Permalink
fix: correctly pad / truncate in JSON-RPC types, add tests to ethereu…
Browse files Browse the repository at this point in the history
…m-address and json-rpc-data (trufflesuite#2716)

fixes trufflesuite#1594
  • Loading branch information
jeffsmale90 authored and MicaiahReid committed Apr 20, 2022
1 parent b7e930c commit d69b206
Show file tree
Hide file tree
Showing 4 changed files with 213 additions and 18 deletions.
25 changes: 23 additions & 2 deletions src/chains/ethereum/address/tests/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
import assert from "assert";
import ethereumAddress from "../";
import {Address} from "../";

describe("@ganache/ethereum-address", () => {
it("needs tests");
describe("toString()", () => {
it("should pad an address to 20 bytes", () => {
const address = new Address("0x1");
const stringifiedAddress = address.toString();

assert.equal(stringifiedAddress, "0x0000000000000000000000000000000000000001");
});

it("should truncate an address to the specified length", () => {
const address = new Address("0x1");
const stringifiedAddress = address.toString(1);

assert.equal(stringifiedAddress, "0x01");
});

it("should stringify a 20 byte address string", () => {
const address = new Address("0x2104859394604359378433865360947116707876");
const stringifiedAddress = address.toString();

assert.equal(stringifiedAddress, "0x2104859394604359378433865360947116707876");
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export class BaseJsonRpcType<
case "string": {
// handle hex-encoded string
if ((value as string).indexOf("0x") === 0) {
toStrings.set(this, () => (value as string).toLowerCase().slice(2));
strCache.set(this, (value as string).toLowerCase());
toBuffers.set(this, () => {
let fixedValue = (value as string).slice(2);
Expand Down
31 changes: 15 additions & 16 deletions src/packages/utils/src/things/json-rpc/json-rpc-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,39 @@ import { BaseJsonRpcType } from "./json-rpc-base-types";
import { strCache, toStrings } from "./json-rpc-base-types";

function validateByteLength(byteLength?: number) {
if (typeof byteLength !== "number" || byteLength < 0) {
throw new Error(`byteLength must be a number greater than 0`);
if (typeof byteLength !== "number" || !(byteLength >= 0)) {
throw new Error(`byteLength must be a number greater than or equal to 0, provided: ${byteLength}`);
}
}
const byteLengths = new WeakMap();

export class Data extends BaseJsonRpcType {
constructor(value: string | Buffer, byteLength?: number) {

constructor(value: string | Buffer, private _byteLength?: number) {
super(value);
if (typeof value === "bigint") {
throw new Error(`Cannot create a ${typeof value} as a Data`);
}
super(value);
if (byteLength !== void 0) {
validateByteLength(byteLength);
byteLengths.set(this, byteLength | 0);
if (_byteLength !== undefined) {
validateByteLength(_byteLength);
}
}
public toString(byteLength?: number): string {
const str = strCache.get(this) as string;
if (str !== void 0) {
return str;
if (byteLength === undefined) {
byteLength = this._byteLength;
}
if (byteLength === undefined && strCache.has(this)) {
return strCache.get(this) as string;
} else {
let str = toStrings.get(this)() as string;
let length = str.length;

if (length % 2 === 1) {
length++;
str = `0${str}`;
}

if (byteLength !== void 0) {
if (byteLength !== undefined) {
validateByteLength(byteLength);
} else {
byteLength = byteLengths.get(this);
}
if (byteLength !== void 0) {
const strLength = byteLength * 2;
const padBy = strLength - length;
if (padBy < 0) {
Expand Down
174 changes: 174 additions & 0 deletions src/packages/utils/tests/json-rpc-data.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import assert from "assert";
import {Data} from "..";

describe("json-rpc-data", () => {
const inputOf32Bytes = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
const validValues = [ "0x", "0x1", "0x1234", Buffer.from([]), Buffer.from([0x12,0x34]), inputOf32Bytes ];
const invalidValues: any[] = [ "1234", 1234n, NaN/*, undefined, null, 1234, [], {}, "0x-1234"*/ ]; // todo: this should be addressed in rewrite of json-rpc-data
// See related https://github.com/trufflesuite/ganache/labels/json-rpc%20refactor
const validBytelengths = (() => { let i = 0; return [...new Array(100)].map(_ => i++); })(); // [0...99]
const invalidBytelengths: any[] = [ -1, "1", {}, [], null, NaN ];

function getExpectedString(value: Buffer|string, bytelength?: number) {
let expected: string;

if (typeof value === "string") {
expected = value.slice(2);
} else if (Buffer.isBuffer(value)) {
expected = (<Buffer>value).toString("hex");
} else {
throw new Error(`Type not supported ${typeof value}`)
}
if (bytelength !== undefined) {
/*
If the value is longer than the specified bytelength, then the result must be left padded.
ie: "0x01" bytelength 2 becomes "0x0001"
If the value is longer than the specified bytelength, then the result must be truncated.
ie: "0x123456" bytelength 2 becomes "0x1234"
*/
const padCharCount = (bytelength - expected.length / 2) * 2; // (desired byte count - actual byte count) * 2 characters per byte
if (padCharCount > 0) {
expected = "0".repeat(padCharCount) + expected;
} else {
expected = expected.slice(0, bytelength * 2);
}
}
return "0x" + expected;
}

describe("constructor", () => {
it("should accept different values", () => {
validValues.forEach(value => {
const d = new Data(value);
});
});

it("should fail with invalid values", () => {
invalidValues.forEach(value => {
assert.throws(() => {
const d = new Data(value);
}, undefined, `Should fail to accept value: ${value} of type: ${typeof value}`);
});
});

it("should accept valid bytelengths", () => {
validBytelengths.forEach(bytelength => {
const d = new Data("0x01", bytelength);
});
});

it("should fail with invalid bytelengths", () => {
invalidBytelengths.forEach(bytelength => {
assert.throws(() => {
const d = new Data("0x01", bytelength);
}, undefined, `Should fail to accept bytelength: ${bytelength} of type: ${typeof bytelength}`);
});
});
});

describe("from()", () => {
it("should accept different representations of value", () => {
validValues.forEach(value => {
const d = Data.from(value);
});
});

it("should fail with invalid values", () => {
invalidValues.forEach(value => {
assert.throws(() => {
const d = Data.from(value);
}, undefined, `Should fail to accept value: ${value} of type: ${typeof value}`);
});
});

it("should accept valid bytelengths", () => {
validBytelengths.forEach(bytelength => {
const d = Data.from("0x01", bytelength);
});
});

it("should fail with invalid bytelengths", () => {
invalidBytelengths.forEach(bytelength => {
assert.throws(() => {
const d = Data.from("0x01", bytelength);
}, undefined, `Should fail to accept bytelength: ${bytelength} of type: ${typeof bytelength}`);
});
});
});

describe("toString()", () => {
it("should stringify without arguments", () => {
validValues.forEach(value => {
const d = new Data(value);
const s = d.toString();
const expected = getExpectedString(value);

assert.equal(s, expected);
});
});

it("should stringify with valid bytelengths", () => {
validValues.forEach(value => {
const d = new Data(value);
validBytelengths.forEach(bytelength => {
const s = d.toString(bytelength);
const expected = getExpectedString(s, bytelength);

assert.equal(s, expected);
});
});
});

it("should fail with invalid bytelengths", () => {
validValues.forEach(value => {
const d = new Data(value);
invalidBytelengths.forEach(bytelength => {
assert.throws(() => {
const s = d.toString(<any>bytelength);
}, undefined, `Should fail to accept bytelength: ${bytelength} of type: ${typeof bytelength}`);
});
});
});

it("should stringify with valid bytelengths provided to constructor", () => {
validValues.forEach(value => {
validBytelengths.forEach(bytelength => {
const d = new Data(value, bytelength);
const s = d.toString();
const expected = getExpectedString(s, bytelength);

assert.equal(s, expected);
});
});
});
});

describe("toBuffer()", () => {
it("should create a buffer", () => {
const expected = Buffer.from([0x12, 0x34]);
const d = new Data("0x1234");
const b = d.toBuffer();

assert.deepEqual(b, expected);
});

// todo: this should be addressed in rewrite of json-rpc-data https://github.com/trufflesuite/ganache/labels/json-rpc%20refactor
it.skip("should create a buffer with a smaller bytelength", () => {
const expected = Buffer.from([0x12, 0x34]);
const d = new Data("0x123456789abcdef", 2);
const b = d.toBuffer();

assert.deepEqual(b, expected);
});

// todo: this should be addressed in rewrite of json-rpc-data https://github.com/trufflesuite/ganache/labels/json-rpc%20refactor
it.skip("should create a buffer with a larger bytelength", () => {
const expected = Buffer.from([0x00, 0x00, 0x12, 0x34]);
const d = new Data("0x1234", 4);
const b = d.toBuffer();

assert.deepEqual(b, expected);
});
});
});

0 comments on commit d69b206

Please sign in to comment.