-
-
Notifications
You must be signed in to change notification settings - Fork 90
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
verifyContract
address normalization (#309)
* Add verifyContract address normalization * Fix suggestions * Fix usage of normalizeTypedMessage * Relocate type assertion * Add extra unit test for parser method * Add isStrictHexString to narrow down type check and remove cast * Remove int conversion * Fix lint * Remove error * Add non parsable unit test case * Add basic signTypedDataV3 test
- Loading branch information
Showing
6 changed files
with
334 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
import { normalizeTypedMessage } from './normalize'; | ||
|
||
const MESSAGE_DATA_MOCK = { | ||
types: { | ||
Permit: [ | ||
{ | ||
name: 'owner', | ||
type: 'address', | ||
}, | ||
{ | ||
name: 'spender', | ||
type: 'address', | ||
}, | ||
{ | ||
name: 'value', | ||
type: 'uint256', | ||
}, | ||
{ | ||
name: 'nonce', | ||
type: 'uint256', | ||
}, | ||
{ | ||
name: 'deadline', | ||
type: 'uint256', | ||
}, | ||
], | ||
EIP712Domain: [ | ||
{ | ||
name: 'name', | ||
type: 'string', | ||
}, | ||
{ | ||
name: 'version', | ||
type: 'string', | ||
}, | ||
{ | ||
name: 'chainId', | ||
type: 'uint256', | ||
}, | ||
{ | ||
name: 'verifyingContract', | ||
type: 'address', | ||
}, | ||
], | ||
}, | ||
domain: { | ||
name: 'Liquid staked Ether 2.0', | ||
version: '2', | ||
chainId: '0x1', | ||
verifyingContract: '996101235222674412020337938588541139382869425796', | ||
}, | ||
primaryType: 'Permit', | ||
message: { | ||
owner: '0x6d404afe1a6a07aa3cbcbf9fd027671df628ebfc', | ||
spender: '0x63605E53D422C4F1ac0e01390AC59aAf84C44A51', | ||
value: | ||
'115792089237316195423570985008687907853269984665640564039457584007913129639935', | ||
nonce: '0', | ||
deadline: '4482689033', | ||
}, | ||
}; | ||
|
||
describe('normalizeTypedMessage', () => { | ||
function parseNormalizerResult(data: Record<string, unknown>) { | ||
return JSON.parse(normalizeTypedMessage(JSON.stringify(data))); | ||
} | ||
|
||
it('should normalize verifyingContract address in domain', () => { | ||
const normalizedData = parseNormalizerResult(MESSAGE_DATA_MOCK); | ||
expect(normalizedData.domain.verifyingContract).toBe( | ||
'0xae7ab96520de3a18e5e111b5eaab095312d7fe84', | ||
); | ||
}); | ||
|
||
it('should normalize verifyingContract address in domain when provided data is an object', () => { | ||
const NON_STRINGIFIED_MESSAGE_DATA_MOCK = MESSAGE_DATA_MOCK; | ||
const normalizedData = JSON.parse( | ||
normalizeTypedMessage( | ||
NON_STRINGIFIED_MESSAGE_DATA_MOCK as unknown as string, | ||
), | ||
); | ||
expect(normalizedData.domain.verifyingContract).toBe( | ||
'0xae7ab96520de3a18e5e111b5eaab095312d7fe84', | ||
); | ||
}); | ||
|
||
it('should handle octal verifyingContract address by normalizing it', () => { | ||
const expectedNormalizedOctalAddress = '0x53'; | ||
const messageDataWithOctalAddress = { | ||
...MESSAGE_DATA_MOCK, | ||
domain: { | ||
...MESSAGE_DATA_MOCK.domain, | ||
verifyingContract: '0o123', | ||
}, | ||
}; | ||
|
||
const normalizedData = parseNormalizerResult(messageDataWithOctalAddress); | ||
|
||
expect(normalizedData.domain.verifyingContract).toBe( | ||
expectedNormalizedOctalAddress, | ||
); | ||
}); | ||
|
||
it('should not modify if verifyingContract is already hexadecimal', () => { | ||
const expectedVerifyingContract = | ||
'0xae7ab96520de3a18e5e111b5eaab095312d7fe84'; | ||
const messageDataWithHexAddress = { | ||
...MESSAGE_DATA_MOCK, | ||
domain: { | ||
...MESSAGE_DATA_MOCK.domain, | ||
verifyingContract: expectedVerifyingContract, | ||
}, | ||
}; | ||
|
||
const normalizedData = parseNormalizerResult(messageDataWithHexAddress); | ||
|
||
expect(normalizedData.domain.verifyingContract).toBe( | ||
expectedVerifyingContract, | ||
); | ||
}); | ||
|
||
it('should not modify if verifyingContract is not parsable', () => { | ||
const expectedVerifyingContract = | ||
'Notparsableaddress1234567890123456789012345678901234567890'; | ||
const messageDataWithHexAddress = { | ||
...MESSAGE_DATA_MOCK, | ||
domain: { | ||
...MESSAGE_DATA_MOCK.domain, | ||
verifyingContract: expectedVerifyingContract, | ||
}, | ||
}; | ||
|
||
const normalizedData = parseNormalizerResult(messageDataWithHexAddress); | ||
|
||
expect(normalizedData.domain.verifyingContract).toBe( | ||
expectedVerifyingContract, | ||
); | ||
}); | ||
|
||
it('should not modify other parts of the message data', () => { | ||
const normalizedData = parseNormalizerResult(MESSAGE_DATA_MOCK); | ||
expect(normalizedData.message).toStrictEqual(MESSAGE_DATA_MOCK.message); | ||
expect(normalizedData.types).toStrictEqual(MESSAGE_DATA_MOCK.types); | ||
expect(normalizedData.primaryType).toStrictEqual( | ||
MESSAGE_DATA_MOCK.primaryType, | ||
); | ||
}); | ||
|
||
it('should return data as is if not parsable', () => { | ||
expect(normalizeTypedMessage('Not parsable data')).toBe( | ||
'Not parsable data', | ||
); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import { add0x, isValidHexAddress, isStrictHexString } from '@metamask/utils'; | ||
import type { Hex } from '@metamask/utils'; | ||
import BN from 'bn.js'; | ||
|
||
type EIP712Domain = { | ||
verifyingContract: string; | ||
}; | ||
|
||
type SignTypedMessageDataV3V4 = { | ||
types: Record<string, unknown>; | ||
domain: EIP712Domain; | ||
primaryType: string; | ||
message: unknown; | ||
}; | ||
|
||
/** | ||
* Normalizes the messageData for the eth_signTypedData | ||
Check warning on line 17 in src/utils/normalize.ts GitHub Actions / Build, lint, and test / Lint (16.x)
Check warning on line 17 in src/utils/normalize.ts GitHub Actions / Build, lint, and test / Lint (18.x)
|
||
* | ||
* @param messageData - The messageData to normalize. | ||
* @returns The normalized messageData. | ||
*/ | ||
export function normalizeTypedMessage(messageData: string) { | ||
let data; | ||
try { | ||
data = parseTypedMessage(messageData); | ||
} catch (e) { | ||
// Ignore normalization errors and pass the message as is | ||
return messageData; | ||
} | ||
|
||
const { verifyingContract } = data.domain ?? {}; | ||
|
||
if (!verifyingContract) { | ||
return messageData; | ||
} | ||
|
||
data.domain.verifyingContract = normalizeContractAddress(verifyingContract); | ||
|
||
return JSON.stringify(data); | ||
} | ||
|
||
/** | ||
* Parses the messageData to obtain the data object for EIP712 normalization | ||
Check warning on line 43 in src/utils/normalize.ts GitHub Actions / Build, lint, and test / Lint (16.x)
Check warning on line 43 in src/utils/normalize.ts GitHub Actions / Build, lint, and test / Lint (18.x)
|
||
* | ||
* @param data - The messageData to parse. | ||
* @returns The data object for EIP712 normalization. | ||
*/ | ||
function parseTypedMessage(data: string) { | ||
if (typeof data !== 'string') { | ||
return data; | ||
} | ||
|
||
return JSON.parse(data) as unknown as SignTypedMessageDataV3V4; | ||
} | ||
|
||
/** | ||
* Normalizes the address to a hexadecimal format | ||
Check warning on line 57 in src/utils/normalize.ts GitHub Actions / Build, lint, and test / Lint (16.x)
Check warning on line 57 in src/utils/normalize.ts GitHub Actions / Build, lint, and test / Lint (18.x)
|
||
* | ||
* @param address - The address to normalize. | ||
* @returns The normalized address. | ||
*/ | ||
function normalizeContractAddress(address: string): Hex | string { | ||
if (isStrictHexString(address) && isValidHexAddress(address)) { | ||
return address; | ||
} | ||
|
||
// Check if the address is in octal format, convert to hexadecimal | ||
if (address.startsWith('0o')) { | ||
// If octal, convert to hexadecimal | ||
return octalToHex(address); | ||
} | ||
|
||
// Check if the address is in decimal format, convert to hexadecimal | ||
try { | ||
const decimalBN = new BN(address, 10); | ||
const hexString = decimalBN.toString(16); | ||
return add0x(hexString); | ||
} catch (e) { | ||
// Ignore errors and return the original address | ||
} | ||
|
||
// Returning the original address without normalization | ||
return address; | ||
} | ||
|
||
function octalToHex(octalAddress: string): Hex { | ||
const decimalAddress = parseInt(octalAddress.slice(2), 8).toString(16); | ||
return add0x(decimalAddress); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.