From aeba1f1ffe00e84545fb961970801172d6862737 Mon Sep 17 00:00:00 2001 From: Aaron Cook Date: Fri, 6 Dec 2024 14:38:18 +0100 Subject: [PATCH] fix: use `dataGas` when calculating message hash of <1.0.0 --- .../Summary/SafeTxHashDataRow/index.test.ts | 71 ++++++++++++++++++- .../Summary/SafeTxHashDataRow/index.tsx | 29 +++++++- 2 files changed, 96 insertions(+), 4 deletions(-) diff --git a/src/components/transactions/TxDetails/Summary/SafeTxHashDataRow/index.test.ts b/src/components/transactions/TxDetails/Summary/SafeTxHashDataRow/index.test.ts index 21d036dc06..f650abe798 100644 --- a/src/components/transactions/TxDetails/Summary/SafeTxHashDataRow/index.test.ts +++ b/src/components/transactions/TxDetails/Summary/SafeTxHashDataRow/index.test.ts @@ -1,6 +1,7 @@ import { faker } from '@faker-js/faker' -import { getDomainHash } from '.' +import { getDomainHash, getMessageHash } from '.' import { AbiCoder, keccak256 } from 'ethers' +import type { SafeTransactionData, SafeVersion } from '@safe-global/safe-core-sdk-types' // <= 1.2.0 // keccak256("EIP712Domain(address verifyingContract)"); @@ -10,6 +11,14 @@ const OLD_DOMAIN_TYPEHASH = '0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8 // keccak256("EIP712Domain(uint256 chainId,address verifyingContract)"); const NEW_DOMAIN_TYPEHASH = '0x47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a79469218' +// < 1.0.0 +// keccak256("SafeTx(address to,uint256 value,bytes data,uint8 operation,uint256 safeTxGas,uint256 dataGas,uint256 gasPrice,address gasToken,address refundReceiver,uint256 nonce)"); +const OLD_SAFE_TX_TYPEHASH = '0x14d461bc7412367e924637b363c7bf29b8f47e2f84869f4426e5633d8af47b20' + +// >= 1.0.0 +// keccak256("SafeTx(address to,uint256 value,bytes data,uint8 operation,uint256 safeTxGas,uint256 baseGas,uint256 gasPrice,address gasToken,address refundReceiver,uint256 nonce)"); +const NEW_SAFE_TX_TYPEHASH = '0xbb8310d486368db6bd6f849402fdd73ad53d316b5a4b2644ad6efe0f941286d8' + describe('SafeTxHashDataRow', () => { describe('getDomainHash', () => { it.each(['1.0.0' as const, '1.1.1' as const, '1.2.0' as const])( @@ -45,4 +54,64 @@ describe('SafeTxHashDataRow', () => { }, ) }) + + describe('getMessageHash', () => { + it.each([ + ['0.1.0' as SafeVersion, OLD_SAFE_TX_TYPEHASH], + ['1.0.0' as const, NEW_SAFE_TX_TYPEHASH], + ['1.1.1' as const, NEW_SAFE_TX_TYPEHASH], + ['1.2.0' as const, NEW_SAFE_TX_TYPEHASH], + ['1.3.0' as const, NEW_SAFE_TX_TYPEHASH], + ['1.4.1' as const, NEW_SAFE_TX_TYPEHASH], + ])('should return the message hash for version %s', (version, typehash) => { + const SafeTx: SafeTransactionData = { + to: faker.finance.ethereumAddress(), + value: faker.string.numeric(), + data: faker.string.hexadecimal({ length: 30 }), + operation: faker.number.int({ min: 0, max: 1 }), + safeTxGas: faker.string.numeric(), + baseGas: faker.string.numeric(), // <1.0.0 is dataGas + gasPrice: faker.string.numeric(), + gasToken: faker.finance.ethereumAddress(), + refundReceiver: faker.finance.ethereumAddress(), + nonce: faker.number.int({ min: 0, max: 69 }), + } + + const result = getMessageHash({ safeVersion: version, safeTxData: SafeTx }) + + expect(result).toEqual( + keccak256( + AbiCoder.defaultAbiCoder().encode( + [ + 'bytes32', + 'address', // to + 'uint256', // value + 'bytes32', // data + 'uint8', // operation + 'uint256', // safeTxGas + 'uint256', // dataGas/baseGas + 'uint256', // gasPrice + 'address', // gasToken + 'address', // refundReceiver + 'uint256', // nonce + ], + [ + typehash, + SafeTx.to, + SafeTx.value, + // EIP-712 expects data to be hashed + keccak256(SafeTx.data), + SafeTx.operation, + SafeTx.safeTxGas, + SafeTx.baseGas, + SafeTx.gasPrice, + SafeTx.gasToken, + SafeTx.refundReceiver, + SafeTx.nonce, + ], + ), + ), + ) + }) + }) }) diff --git a/src/components/transactions/TxDetails/Summary/SafeTxHashDataRow/index.tsx b/src/components/transactions/TxDetails/Summary/SafeTxHashDataRow/index.tsx index d2a09f2b14..0fc21a99c6 100644 --- a/src/components/transactions/TxDetails/Summary/SafeTxHashDataRow/index.tsx +++ b/src/components/transactions/TxDetails/Summary/SafeTxHashDataRow/index.tsx @@ -24,6 +24,31 @@ export function getDomainHash({ }) } +const NEW_SAFE_TX_TYPE_HASH_VERSION = '>=1.0.0' + +export function getMessageHash({ + safeVersion, + safeTxData, +}: { + safeVersion: SafeVersion + safeTxData: SafeTransactionData +}): string { + const usesBaseGas = semverSatisfies(safeVersion, NEW_SAFE_TX_TYPE_HASH_VERSION) + const SafeTx = getEip712TxTypes(safeVersion).SafeTx + + // Clone to not modify the original + const tx: any = { ...safeTxData } + + if (!usesBaseGas) { + tx.dataGas = tx.baseGas + delete tx.baseGas + + SafeTx[5].name = 'dataGas' + } + + return TypedDataEncoder.hashStruct('SafeTx', { SafeTx }, tx) +} + export const SafeTxHashDataRow = ({ safeTxHash, safeTxData, @@ -37,9 +62,7 @@ export const SafeTxHashDataRow = ({ const safeAddress = useSafeAddress() const domainHash = getDomainHash({ chainId, safeAddress, safeVersion }) - const messageHash = safeTxData - ? TypedDataEncoder.hashStruct('SafeTx', { SafeTx: getEip712TxTypes(safeVersion).SafeTx }, safeTxData) - : undefined + const messageHash = safeTxData ? getMessageHash({ safeVersion, safeTxData }) : undefined return ( <>