-
Notifications
You must be signed in to change notification settings - Fork 196
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(common): kms correctly serializes transactions #2721
Changes from 7 commits
8fa22c6
ed07da6
679c370
0a672cd
309176b
3d60218
f614769
70225d0
3fbb101
8a0b00f
a136a2f
ab8487e
618dd47
eba9e6c
3b79f0c
6fc9ba6
664c49a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
import { KMSClient } from "@aws-sdk/client-kms"; | ||
import { LocalAccount, hashMessage, hashTypedData, keccak256, serializeTransaction } from "viem"; | ||
import { LocalAccount, hashMessage, hashTypedData, keccak256, serializeTransaction, signatureToHex } from "viem"; | ||
import { toAccount } from "viem/accounts"; | ||
import { signWithKms } from "./signWithKms"; | ||
import { getAddressFromKms } from "./getAddressFromKms"; | ||
|
@@ -25,38 +25,46 @@ export async function createKmsAccount({ | |
const account = toAccount({ | ||
address, | ||
async signMessage({ message }) { | ||
const hash = hashMessage(message); | ||
const signature = await signWithKms({ | ||
client, | ||
keyId, | ||
hash, | ||
hash: hashMessage(message), | ||
address, | ||
}); | ||
|
||
return signature; | ||
return signatureToHex(signature); | ||
}, | ||
async signTransaction(transaction) { | ||
const unsignedTx = serializeTransaction(transaction); | ||
const hash = keccak256(unsignedTx); | ||
async signTransaction(transaction, { serializer = serializeTransaction } = {}) { | ||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type | ||
const signableTransaction = (() => { | ||
// For EIP-4844 Transactions, we want to sign the transaction payload body (tx_payload_body) without the sidecars (ie. without the network wrapper). | ||
// See: https://github.com/ethereum/EIPs/blob/e00f4daa66bd56e2dbd5f1d36d09fd613811a48b/EIPS/eip-4844.md#networking | ||
if (transaction.type === "eip4844") | ||
return { | ||
...transaction, | ||
sidecars: false, | ||
}; | ||
return transaction; | ||
})(); | ||
|
||
const signature = await signWithKms({ | ||
client, | ||
keyId, | ||
hash, | ||
hash: keccak256(serializer(signableTransaction)), | ||
address, | ||
}); | ||
|
||
return signature; | ||
return serializer(transaction, signature); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I see now we're following viem There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nope, again this is from viem. I think it's because the data you sign should (or shouldn't, idk) include some fields that are in the full transaction data. |
||
}, | ||
async signTypedData(typedData) { | ||
const hash = hashTypedData(typedData); | ||
const signature = await signWithKms({ | ||
client, | ||
keyId, | ||
hash, | ||
hash: hashTypedData(typedData), | ||
address, | ||
}); | ||
|
||
return signature; | ||
return signatureToHex(signature); | ||
}, | ||
}); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { testClient } from "../../test/common"; | ||
|
||
export async function minePending(): Promise<void> { | ||
const content = await testClient.getTxpoolContent(); | ||
if (!Object.keys(content.pending).length) return; | ||
|
||
await testClient.mine({ blocks: 1 }); | ||
await minePending(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { Hex } from "viem"; | ||
import { getTransactionReceipt } from "viem/actions"; | ||
import { testClient } from "../../test/common"; | ||
import { minePending } from "./minePending"; | ||
|
||
export async function waitForTransaction(hash: Hex): Promise<void> { | ||
await minePending(); | ||
const receipt = await getTransactionReceipt(testClient, { hash }); | ||
if (receipt.status === "reverted") { | ||
// TODO: better error | ||
throw new Error(`Transaction reverted (${hash})`); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { createTestClient, http, publicActions, walletActions } from "viem"; | ||
|
||
export const anvilHost = "127.0.0.1"; | ||
export const anvilPort = 8565; | ||
|
||
// ID of the current test worker. Used by the `@viem/anvil` proxy server. | ||
export const poolId = Number(process.env.VITEST_POOL_ID ?? 1); | ||
|
||
export const anvilRpcUrl = `http://${anvilHost}:${anvilPort}/${poolId}`; | ||
|
||
export const testClient = createTestClient({ | ||
mode: "anvil", | ||
// TODO: if tests get slow, try switching to websockets? | ||
transport: http(anvilRpcUrl), | ||
pollingInterval: 10, | ||
}) | ||
.extend(publicActions) | ||
.extend(walletActions); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { startProxy as startAnvilProxy } from "@viem/anvil"; | ||
import { anvilHost, anvilPort } from "./common"; | ||
|
||
export default async function globalSetup(): Promise<() => Promise<void>> { | ||
const shutdownAnvilProxy = await startAnvilProxy({ | ||
host: anvilHost, | ||
port: anvilPort, | ||
}); | ||
|
||
return async () => { | ||
await shutdownAnvilProxy(); | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { beforeAll, beforeEach } from "vitest"; | ||
import { testClient } from "./common"; | ||
|
||
// Some test suites deploy contracts in a `beforeAll` handler, so we restore chain state here. | ||
beforeAll(async () => { | ||
const state = await testClient.dumpState(); | ||
return async (): Promise<void> => { | ||
await testClient.loadState({ state }); | ||
}; | ||
}); | ||
|
||
// Some tests execute transactions, so we restore chain state here. | ||
beforeEach(async () => { | ||
const state = await testClient.dumpState(); | ||
return async (): Promise<void> => { | ||
await testClient.loadState({ state }); | ||
}; | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { defineConfig } from "vitest/config"; | ||
|
||
export default defineConfig({ | ||
test: { | ||
globalSetup: ["test/globalSetup.ts"], | ||
setupFiles: ["test/setup.ts"], | ||
// Temporarily set a low teardown timeout because anvil hangs otherwise | ||
// Could move this timeout to anvil setup after https://github.com/wevm/anvil.js/pull/46 | ||
teardownTimeout: 500, | ||
hookTimeout: 15000, | ||
}, | ||
}); |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ooc why this and not
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copied verbatim from the Viem function! It could be more encapsulated imo: https://github.com/wevm/viem/blob/main/src/accounts/utils/signTransaction.ts#L53-L62
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ahh got it!
maybe worth a note above about keeping this logic aligned with viem internals?