Skip to content

Commit 43fb9c2

Browse files
committed
Fix EIP-712 type aliases for uint and int (#4541).
1 parent 7882905 commit 43fb9c2

File tree

2 files changed

+102
-13
lines changed

2 files changed

+102
-13
lines changed

src.ts/_tests/test-hash-typeddata.ts

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import assert from "assert";
22
import { loadTests } from "./utils.js";
33
import type { TestCaseTypedData } from "./types.js";
4-
54
import { TypedDataEncoder } from "../index.js";
65

76

@@ -18,3 +17,85 @@ describe("Tests Typed Data (EIP-712)", function() {
1817
});
1918
}
2019
});
20+
21+
interface TestAlias {
22+
name: string;
23+
types: Record<string, Array<{ name: string, type: string }>>;
24+
typesAlias: Record<string, Array<{ name: string, type: string }>>;
25+
data: Record<string, any>;
26+
encoded: string;
27+
}
28+
29+
describe("Tests Typed Data (EIP-712) aliases", function() {
30+
const tests: Array<TestAlias> = [
31+
{
32+
name: "uint",
33+
types: {
34+
foo: [
35+
{ name: "a", type: "uint256" },
36+
{ name: "b", type: "string" },
37+
],
38+
},
39+
typesAlias: {
40+
foo: [
41+
{ name: "a", type: "uint" },
42+
{ name: "b", type: "string" },
43+
],
44+
},
45+
data: {
46+
a: 35,
47+
b: "hello"
48+
},
49+
encoded: "0x859b6b4a5d436f85a809f6383b4b35a153aa6fe9c95946c366d9dfd634b89f4700000000000000000000000000000000000000000000000000000000000000231c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8"
50+
},
51+
{
52+
name: "int",
53+
types: {
54+
foo: [
55+
{ name: "a", type: "int256" },
56+
{ name: "b", type: "string" },
57+
],
58+
},
59+
typesAlias: {
60+
foo: [
61+
{ name: "a", type: "int" },
62+
{ name: "b", type: "string" },
63+
],
64+
},
65+
data: {
66+
a: 35,
67+
b: "hello"
68+
},
69+
encoded: "0xa272ada5f88085e4cb18acdb87bd057a8cbfec249fee53de0149409080947cf500000000000000000000000000000000000000000000000000000000000000231c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8"
70+
},
71+
];
72+
73+
for (const test of tests) {
74+
it(`tests encoding typed-data: ${ test.name }`, function() {
75+
const encoder = TypedDataEncoder.from(test.types);
76+
assert.equal(encoder.primaryType, "foo", "primaryType");
77+
assert.equal(encoder.encodeData("foo", test.data), test.encoded, "encoded");
78+
});
79+
}
80+
81+
it(`tests overriding an alias as a type`, function() {
82+
const encoder = TypedDataEncoder.from({
83+
uint: [
84+
{ name: "value", type: "uint256" }
85+
],
86+
foo: [
87+
{ name: "a", type: "uint" },
88+
{ name: "b", type: "string" },
89+
]
90+
});
91+
assert.equal(encoder.primaryType, "foo", "primaryType");
92+
93+
const data = encoder.encodeData("foo", {
94+
a: { value: 42 },
95+
b: "hello"
96+
});
97+
98+
const encoded = "0x87a4bfff36f1a2ecde6468d6acd51ecc5ef8f3a15d8115a412c686d82d3fdbe4628fc3080b86a044fb60153bb7dc3f904e9ed1cebadf35c17099a060ba4df90b1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8";
99+
assert.equal(data, encoded, "encoded");
100+
});
101+
});

src.ts/hash/typed-data.ts

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -124,12 +124,12 @@ const domainChecks: Record<string, (value: any) => any> = {
124124
function getBaseEncoder(type: string): null | ((value: any) => string) {
125125
// intXX and uintXX
126126
{
127-
const match = type.match(/^(u?)int(\d*)$/);
127+
const match = type.match(/^(u?)int(\d+)$/);
128128
if (match) {
129129
const signed = (match[1] === "");
130130

131-
const width = parseInt(match[2] || "256");
132-
assertArgument(width % 8 === 0 && width !== 0 && width <= 256 && (match[2] == null || match[2] === String(width)), "invalid numeric width", "type", type);
131+
const width = parseInt(match[2]);
132+
assertArgument(width % 8 === 0 && width !== 0 && width <= 256 && match[2] === String(width), "invalid numeric width", "type", type);
133133

134134
const boundsUpper = mask(BN_MAX_UINT256, signed ? (width - 1): width);
135135
const boundsLower = signed ? ((boundsUpper + BN_1) * BN__1): BN_0;
@@ -220,8 +220,7 @@ export class TypedDataEncoder {
220220
* do not violate the [[link-eip-712]] structural constraints as
221221
* well as computes the [[primaryType]].
222222
*/
223-
constructor(types: Record<string, Array<TypedDataField>>) {
224-
this.#types = JSON.stringify(types);
223+
constructor(_types: Record<string, Array<TypedDataField>>) {
225224
this.#fullTypes = new Map();
226225
this.#encoderCache = new Map();
227226

@@ -234,30 +233,39 @@ export class TypedDataEncoder {
234233
// Link all subtypes within a given struct
235234
const subtypes: Map<string, Set<string>> = new Map();
236235

237-
Object.keys(types).forEach((type) => {
236+
const types: Record<string, Array<TypedDataField>> = { };
237+
Object.keys(_types).forEach((type) => {
238+
// Normalize int/uint unless they are a complex type themselves
239+
types[type] = _types[type].map(({ name, type }) => {
240+
if (type === "int" && !_types["int"]) { type = "int256"; }
241+
if (type === "uint" && !_types["uint"]) { type = "uint256"; }
242+
return { name, type };
243+
});
244+
238245
links.set(type, new Set());
239246
parents.set(type, [ ]);
240247
subtypes.set(type, new Set());
241248
});
249+
this.#types = JSON.stringify(types);
242250

243251
for (const name in types) {
244252
const uniqueNames: Set<string> = new Set();
245253

246254
for (const field of types[name]) {
247255

248256
// Check each field has a unique name
249-
assertArgument(!uniqueNames.has(field.name), `duplicate variable name ${ JSON.stringify(field.name) } in ${ JSON.stringify(name) }`, "types", types);
257+
assertArgument(!uniqueNames.has(field.name), `duplicate variable name ${ JSON.stringify(field.name) } in ${ JSON.stringify(name) }`, "types", _types);
250258
uniqueNames.add(field.name);
251259

252260
// Get the base type (drop any array specifiers)
253261
const baseType = (<any>(field.type.match(/^([^\x5b]*)(\x5b|$)/)))[1] || null;
254-
assertArgument(baseType !== name, `circular type reference to ${ JSON.stringify(baseType) }`, "types", types);
262+
assertArgument(baseType !== name, `circular type reference to ${ JSON.stringify(baseType) }`, "types", _types);
255263

256264
// Is this a base encoding type?
257265
const encoder = getBaseEncoder(baseType);
258266
if (encoder) { continue; }
259267

260-
assertArgument(parents.has(baseType), `unknown type ${ JSON.stringify(baseType) }`, "types", types);
268+
assertArgument(parents.has(baseType), `unknown type ${ JSON.stringify(baseType) }`, "types", _types);
261269

262270
// Add linkage
263271
(parents.get(baseType) as Array<string>).push(name);
@@ -267,14 +275,14 @@ export class TypedDataEncoder {
267275

268276
// Deduce the primary type
269277
const primaryTypes = Array.from(parents.keys()).filter((n) => ((parents.get(n) as Array<string>).length === 0));
270-
assertArgument(primaryTypes.length !== 0, "missing primary type", "types", types);
271-
assertArgument(primaryTypes.length === 1, `ambiguous primary types or unused types: ${ primaryTypes.map((t) => (JSON.stringify(t))).join(", ") }`, "types", types);
278+
assertArgument(primaryTypes.length !== 0, "missing primary type", "types", _types);
279+
assertArgument(primaryTypes.length === 1, `ambiguous primary types or unused types: ${ primaryTypes.map((t) => (JSON.stringify(t))).join(", ") }`, "types", _types);
272280

273281
defineProperties<TypedDataEncoder>(this, { primaryType: primaryTypes[0] });
274282

275283
// Check for circular type references
276284
function checkCircular(type: string, found: Set<string>) {
277-
assertArgument(!found.has(type), `circular type reference to ${ JSON.stringify(type) }`, "types", types);
285+
assertArgument(!found.has(type), `circular type reference to ${ JSON.stringify(type) }`, "types", _types);
278286

279287
found.add(type);
280288

0 commit comments

Comments
 (0)