Skip to content

Commit 8a33b80

Browse files
committed
test with Permit objects
1 parent 00aef29 commit 8a33b80

File tree

3 files changed

+81
-71
lines changed

3 files changed

+81
-71
lines changed

test/helpers/erc7739.js

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ class PersonalSignHelper {
2424
}
2525

2626
class TypedDataSignHelper {
27-
constructor(contentsTypeName, contentsTypeValues) {
28-
this.types = {
27+
constructor(contentsTypes, contentsTypeName = Object.keys(contentsTypes).at(0)) {
28+
this.allTypes = {
2929
TypedDataSign: formatType({
3030
contents: contentsTypeName,
3131
fields: 'bytes1',
@@ -36,14 +36,15 @@ class TypedDataSignHelper {
3636
salt: 'bytes32',
3737
extensions: 'uint256[]',
3838
}),
39-
[contentsTypeName]: formatType(contentsTypeValues),
39+
...contentsTypes,
4040
};
41+
this.types = contentsTypes;
4142
this.contentsTypeName = contentsTypeName;
4243
this.contentsType = ethers.TypedDataEncoder.from(this.types).encodeType(contentsTypeName);
4344
}
4445

45-
static from(contentsTypeName, contentsTypeValues) {
46-
return new TypedDataSignHelper(contentsTypeName, contentsTypeValues);
46+
static from(contentsTypes, contentsTypeName = Object.keys(contentsTypes).at(0)) {
47+
return new TypedDataSignHelper(contentsTypes, contentsTypeName);
4748
}
4849

4950
static prepare(contents, signerDomain) {
@@ -60,18 +61,14 @@ class TypedDataSignHelper {
6061

6162
hash(data, appDomain) {
6263
try {
63-
return ethers.TypedDataEncoder.hash(appDomain, this.types, data);
64+
return ethers.TypedDataEncoder.hash(appDomain, this.allTypes, data);
6465
} catch {
65-
return ethers.TypedDataEncoder.hash(
66-
appDomain,
67-
{ [this.contentsTypeName]: this.types[this.contentsTypeName] },
68-
data,
69-
);
66+
return ethers.TypedDataEncoder.hash(appDomain, this.types, data);
7067
}
7168
}
7269

7370
sign(signer, data, appDomain) {
74-
return Promise.resolve(signer.signTypedData(appDomain, this.types, data)).then(signature =>
71+
return Promise.resolve(signer.signTypedData(appDomain, this.allTypes, data)).then(signature =>
7572
ethers.concat([
7673
signature,
7774
domainSeparator(appDomain),

test/utils/cryptography/draft-ERC7739Signer.test.js

Lines changed: 50 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,23 @@ const { ethers } = require('hardhat');
22
const { expect } = require('chai');
33
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
44

5-
const { getDomain } = require('../../helpers/eip712');
5+
const { getDomain, Permit } = require('../../helpers/eip712');
66
const { PersonalSignHelper, TypedDataSignHelper } = require('../../helpers/erc7739');
77

88
// Constant
99
const MAGIC_VALUE = '0x1626ba7e';
1010

11+
// SignedTypedData helpers for a ERC20Permit application.
12+
const helper = TypedDataSignHelper.from({ Permit });
13+
1114
// Fixture
1215
async function fixture() {
1316
// Using getSigners fails, probably due to a bad implementation of signTypedData somewhere in hardhat
1417
const eoa = await ethers.Wallet.createRandom();
1518
const mock = await ethers.deployContract('$ERC7739SignerMock', [eoa]);
1619
const domain = await getDomain(mock);
1720

18-
// Dummy app domain, different from the ERC7739Signer's domain
19-
const appDomain = {
20-
name: 'SomeApp',
21-
version: '1',
22-
chainId: domain.chainId,
23-
verifyingContract: '0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512',
24-
};
25-
26-
return { eoa, mock, domain, appDomain };
21+
return { eoa, mock, domain };
2722
}
2823

2924
describe('ERC7739Signer', function () {
@@ -32,44 +27,57 @@ describe('ERC7739Signer', function () {
3227
});
3328

3429
describe('isValidSignature', function () {
35-
it('returns true for a valid personal signature', async function () {
36-
const text = 'Hello, world!';
30+
describe('PersonalSign', async function () {
31+
it('returns true for a valid personal signature', async function () {
32+
const text = 'Hello, world!';
3733

38-
const hash = PersonalSignHelper.hash(text);
39-
const signature = await PersonalSignHelper.sign(this.eoa, text, this.domain);
34+
const hash = PersonalSignHelper.hash(text);
35+
const signature = await PersonalSignHelper.sign(this.eoa, text, this.domain);
4036

41-
expect(await this.mock.isValidSignature(hash, signature)).to.equal(MAGIC_VALUE);
42-
});
37+
expect(await this.mock.isValidSignature(hash, signature)).to.equal(MAGIC_VALUE);
38+
});
4339

44-
it('returns false for an invalid personal signature', async function () {
45-
const hash = PersonalSignHelper.hash('Message the app expects');
46-
const signature = await PersonalSignHelper.sign(this.eoa, 'Message signed is different', this.domain);
40+
it('returns false for an invalid personal signature', async function () {
41+
const hash = PersonalSignHelper.hash('Message the app expects');
42+
const signature = await PersonalSignHelper.sign(this.eoa, 'Message signed is different', this.domain);
4743

48-
expect(await this.mock.isValidSignature(hash, signature)).to.not.equal(MAGIC_VALUE);
44+
expect(await this.mock.isValidSignature(hash, signature)).to.not.equal(MAGIC_VALUE);
45+
});
4946
});
5047

51-
it('returns true for a valid typed data signature', async function () {
52-
const helper = TypedDataSignHelper.from('SomeType', { something: 'bytes32' });
53-
54-
const appMessage = { something: ethers.randomBytes(32) };
55-
const message = TypedDataSignHelper.prepare(appMessage, this.domain);
56-
57-
const hash = helper.hash(appMessage, this.appDomain);
58-
const signature = await helper.sign(this.eoa, message, this.appDomain);
59-
60-
expect(await this.mock.isValidSignature(hash, signature)).to.equal(MAGIC_VALUE);
61-
});
62-
63-
it('returns false for an invalid typed data signature', async function () {
64-
const helper = TypedDataSignHelper.from('SomeType', { something: 'bytes32' });
65-
66-
const appMessage = { something: ethers.randomBytes(32) };
67-
const signedMessage = TypedDataSignHelper.prepare({ something: ethers.randomBytes(32) }, this.domain);
68-
69-
const hash = helper.hash(appMessage, this.appDomain);
70-
const signature = await helper.sign(this.eoa, signedMessage, this.appDomain);
71-
72-
expect(await this.mock.isValidSignature(hash, signature)).to.not.equal(MAGIC_VALUE);
48+
describe('TypedDataSign', async function () {
49+
beforeEach(async function () {
50+
// Dummy app domain, different from the ERC7739Signer's domain
51+
this.appDomain = {
52+
name: 'SomeApp',
53+
version: '1',
54+
chainId: this.domain.chainId,
55+
verifyingContract: '0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512',
56+
};
57+
this.appMessage = {
58+
owner: '0x1ab5E417d9AF00f1ca9d159007e12c401337a4bb',
59+
spender: '0xD68E96620804446c4B1faB3103A08C98d4A8F55f',
60+
value: 1_000_000n,
61+
nonce: 0n,
62+
deadline: ethers.MaxUint256,
63+
};
64+
this.appHash = helper.hash(this.appMessage, this.appDomain);
65+
});
66+
67+
it('returns true for a valid typed data signature', async function () {
68+
const message = TypedDataSignHelper.prepare(this.appMessage, this.domain);
69+
const signature = await helper.sign(this.eoa, message, this.appDomain);
70+
71+
expect(await this.mock.isValidSignature(this.appHash, signature)).to.equal(MAGIC_VALUE);
72+
});
73+
74+
it('returns false for an invalid typed data signature', async function () {
75+
// signed message is for a lower value.
76+
const message = TypedDataSignHelper.prepare({ ...this.appMessage, value: 1n }, this.domain);
77+
const signature = await helper.sign(this.eoa, message, this.appDomain);
78+
79+
expect(await this.mock.isValidSignature(this.appHash, signature)).to.not.equal(MAGIC_VALUE);
80+
});
7381
});
7482
});
7583
});

test/utils/cryptography/draft-ERC7739Utils.test.js

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,12 @@ const { expect } = require('chai');
22
const { ethers } = require('hardhat');
33
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
44

5+
const { Permit } = require('../../helpers/eip712');
56
const { PersonalSignHelper, TypedDataSignHelper } = require('../../helpers/erc7739');
67

8+
// Helper for ERC20Permit applications
9+
const helper = TypedDataSignHelper.from({ Permit });
10+
711
const fixture = async () => {
812
const mock = await ethers.deployContract('$ERC7739Utils');
913
const domain = {
@@ -18,7 +22,14 @@ const fixture = async () => {
1822
chainId: await ethers.provider.getNetwork().then(({ chainId }) => chainId),
1923
verifyingContract: '0x92C32cadBc39A15212505B5530aA765c441F306f',
2024
};
21-
return { mock, domain, otherDomain };
25+
const permit = {
26+
owner: '0x1ab5E417d9AF00f1ca9d159007e12c401337a4bb',
27+
spender: '0xD68E96620804446c4B1faB3103A08C98d4A8F55f',
28+
value: 1_000_000n,
29+
nonce: 0n,
30+
deadline: ethers.MaxUint256,
31+
};
32+
return { mock, domain, otherDomain, permit };
2233
};
2334

2435
describe('ERC7739Utils', function () {
@@ -91,15 +102,14 @@ describe('ERC7739Utils', function () {
91102

92103
describe('typedDataSignStructHash', function () {
93104
it('should match the typed data nested struct hash', async function () {
94-
const message = TypedDataSignHelper.prepare({ something: ethers.randomBytes(32) }, this.domain);
105+
const message = TypedDataSignHelper.prepare(this.permit, this.domain);
95106

96-
const { types, contentsType } = TypedDataSignHelper.from('SomeType', { something: 'bytes32' });
97-
const contentsHash = ethers.TypedDataEncoder.hashStruct('SomeType', types, message.contents);
98-
const hash = ethers.TypedDataEncoder.hashStruct('TypedDataSign', types, message);
107+
const contentsHash = ethers.TypedDataEncoder.hashStruct('Permit', helper.types, message.contents);
108+
const hash = ethers.TypedDataEncoder.hashStruct('TypedDataSign', helper.allTypes, message);
99109

100110
expect(
101111
await this.mock.$typedDataSignStructHash(
102-
contentsType,
112+
helper.contentsType,
103113
contentsHash,
104114
message.fields,
105115
message.name,
@@ -114,19 +124,14 @@ describe('ERC7739Utils', function () {
114124

115125
describe('typedDataSignTypehash', function () {
116126
it('should match', async function () {
117-
const { types, contentsType, contentsName } = TypedDataSignHelper.from('FooType', {
118-
foo: 'address',
119-
bar: 'uint256',
120-
});
121-
const typedDataSigType = ethers.TypedDataEncoder.from(types).encodeType('TypedDataSign');
127+
const typedDataSigType = ethers.TypedDataEncoder.from(helper.allTypes).encodeType('TypedDataSign');
128+
const typedDataSigTypeHash = ethers.keccak256(ethers.toUtf8Bytes(typedDataSigType));
122129

123-
expect(await this.mock.$typedDataSignTypehash(contentsType)).to.equal(
124-
ethers.keccak256(ethers.toUtf8Bytes(typedDataSigType)),
125-
);
130+
expect(await this.mock.$typedDataSignTypehash(helper.contentsType)).to.equal(typedDataSigTypeHash);
126131

127-
expect(await this.mock.$typedDataSignTypehash(contentsType, ethers.Typed.string(contentsName))).to.equal(
128-
ethers.keccak256(ethers.toUtf8Bytes(typedDataSigType)),
129-
);
132+
expect(
133+
await this.mock.$typedDataSignTypehash(helper.contentsType, ethers.Typed.string(helper.contentsTypeName)),
134+
).to.equal(typedDataSigTypeHash);
130135
});
131136

132137
it('should revert with InvalidContentsType if the type is invalid', async function () {

0 commit comments

Comments
 (0)