From e193ca92fd1d237fa67e97c562d26b3d0cce5f73 Mon Sep 17 00:00:00 2001 From: Yash Date: Mon, 17 Feb 2025 20:32:04 +0530 Subject: [PATCH 1/4] bytes32 salt for deterministic deployment --- .../src/utils/any-evm/compute-deployment-address.ts | 5 ++++- .../any-evm/compute-published-contract-deploy-info.ts | 1 + .../src/utils/any-evm/get-init-bytecode-with-salt.ts | 7 +++++-- .../src/utils/any-evm/zksync/computeDeploymentAddress.ts | 8 ++++++-- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/thirdweb/src/utils/any-evm/compute-deployment-address.ts b/packages/thirdweb/src/utils/any-evm/compute-deployment-address.ts index 03120574002..ccd0793db79 100644 --- a/packages/thirdweb/src/utils/any-evm/compute-deployment-address.ts +++ b/packages/thirdweb/src/utils/any-evm/compute-deployment-address.ts @@ -1,6 +1,7 @@ import { type Hex, encodePacked } from "viem"; import { getAddress } from "../address.js"; import { ensureBytecodePrefix } from "../bytecode/prefix.js"; +import { isHex } from "../encoding/hex.js"; import { keccak256 } from "../hashing/keccak256.js"; import { getSaltHash } from "./get-salt-hash.js"; import { keccakId } from "./keccak-id.js"; @@ -33,7 +34,9 @@ export function computeDeploymentAddress( ) { const bytecode = ensureBytecodePrefix(options.bytecode); const saltHash = options.salt - ? keccakId(options.salt) + ? isHex(options.salt) && options.salt.length === 66 + ? options.salt + : keccakId(options.salt) : getSaltHash(bytecode); // 1. create init bytecode hash with contract's bytecode and encoded args diff --git a/packages/thirdweb/src/utils/any-evm/compute-published-contract-deploy-info.ts b/packages/thirdweb/src/utils/any-evm/compute-published-contract-deploy-info.ts index 77203b08371..2c3c212547d 100644 --- a/packages/thirdweb/src/utils/any-evm/compute-published-contract-deploy-info.ts +++ b/packages/thirdweb/src/utils/any-evm/compute-published-contract-deploy-info.ts @@ -96,5 +96,6 @@ export async function computeDeploymentInfoFromBytecode(args: { initBytecodeWithsalt, encodedArgs, create2FactoryAddress, + salt, }; } diff --git a/packages/thirdweb/src/utils/any-evm/get-init-bytecode-with-salt.ts b/packages/thirdweb/src/utils/any-evm/get-init-bytecode-with-salt.ts index 1e2f76a63e3..96d61db97f7 100644 --- a/packages/thirdweb/src/utils/any-evm/get-init-bytecode-with-salt.ts +++ b/packages/thirdweb/src/utils/any-evm/get-init-bytecode-with-salt.ts @@ -1,6 +1,6 @@ import { encodePacked } from "viem/utils"; import { ensureBytecodePrefix } from "../bytecode/prefix.js"; -import { type Hex, uint8ArrayToHex } from "../encoding/hex.js"; +import { type Hex, isHex, uint8ArrayToHex } from "../encoding/hex.js"; import { getSaltHash } from "./get-salt-hash.js"; import { keccakId } from "./keccak-id.js"; @@ -29,8 +29,11 @@ export function getInitBytecodeWithSalt( options: GetInitiBytecodeWithSaltOptions, ): Hex { const bytecode = ensureBytecodePrefix(options.bytecode); + const saltHash = options.salt - ? keccakId(options.salt) + ? isHex(options.salt) && options.salt.length === 66 + ? options.salt + : keccakId(options.salt) : getSaltHash(bytecode); const encodedArgs = diff --git a/packages/thirdweb/src/utils/any-evm/zksync/computeDeploymentAddress.ts b/packages/thirdweb/src/utils/any-evm/zksync/computeDeploymentAddress.ts index 0298a295c3c..bc6b307fc43 100644 --- a/packages/thirdweb/src/utils/any-evm/zksync/computeDeploymentAddress.ts +++ b/packages/thirdweb/src/utils/any-evm/zksync/computeDeploymentAddress.ts @@ -1,5 +1,5 @@ import type { Address } from "../../address.js"; -import type { Hex } from "../../encoding/hex.js"; +import { type Hex, isHex } from "../../encoding/hex.js"; import { keccakId } from "../keccak-id.js"; import { create2Address } from "./create2Address.js"; @@ -13,7 +13,11 @@ type ComputeDeploymentAddressOptions = { export function computeDeploymentAddress( options: ComputeDeploymentAddressOptions, ) { - const saltHash = options.salt ? keccakId(options.salt) : keccakId("thirdweb"); + const saltHash = options.salt + ? isHex(options.salt) && options.salt.length === 66 + ? options.salt + : keccakId(options.salt) + : keccakId("thirdweb"); return create2Address({ sender: options.create2FactoryAddress, From ce925a468986a7611e4eeb08a39c9cf871ed9fc0 Mon Sep 17 00:00:00 2001 From: Yash Date: Mon, 17 Feb 2025 20:32:40 +0530 Subject: [PATCH 2/4] changeset --- .changeset/popular-ligers-taste.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/popular-ligers-taste.md diff --git a/.changeset/popular-ligers-taste.md b/.changeset/popular-ligers-taste.md new file mode 100644 index 00000000000..a854da88c31 --- /dev/null +++ b/.changeset/popular-ligers-taste.md @@ -0,0 +1,5 @@ +--- +"thirdweb": patch +--- + +bytes32 salt for deterministic deployment From 82868404ba28e5aeb1db12153d748c5b7e0385c2 Mon Sep 17 00:00:00 2001 From: Yash Date: Mon, 17 Feb 2025 22:08:42 +0530 Subject: [PATCH 3/4] tests --- .../deployment/deploy-deterministic.test.ts | 47 ++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/packages/thirdweb/src/contract/deployment/deploy-deterministic.test.ts b/packages/thirdweb/src/contract/deployment/deploy-deterministic.test.ts index 2dc4b384efd..97834f3e773 100644 --- a/packages/thirdweb/src/contract/deployment/deploy-deterministic.test.ts +++ b/packages/thirdweb/src/contract/deployment/deploy-deterministic.test.ts @@ -7,6 +7,8 @@ import { import { TEST_CLIENT } from "../../../test/src/test-clients.js"; import { TEST_ACCOUNT_A } from "../../../test/src/test-wallets.js"; import { simulateTransaction } from "../../transaction/actions/simulate.js"; +import { computePublishedContractAddress } from "../../utils/any-evm/compute-published-contract-address.js"; +import { keccakId } from "../../utils/any-evm/keccak-id.js"; import { ENTRYPOINT_ADDRESS_v0_6 } from "../../wallets/smart/lib/constants.js"; import { prepareDeterministicDeployTransaction } from "./deploy-deterministic.js"; @@ -51,6 +53,16 @@ describe.runIf(process.env.TW_SECRET_KEY)("deployFromMetadata", () => { salt: "some-salt", }); const tx2 = prepareDeterministicDeployTransaction({ + chain: FORKED_ETHEREUM_CHAIN, + client: TEST_CLIENT, + contractId: "AccountFactory", + constructorParams: { + defaultAdmin: TEST_ACCOUNT_A.address, + entrypoint: ENTRYPOINT_ADDRESS_v0_6, + }, + salt: keccakId("some-salt"), + }); + const tx3 = prepareDeterministicDeployTransaction({ chain: FORKED_OPTIMISM_CHAIN, client: TEST_CLIENT, contractId: "AccountFactory", @@ -59,11 +71,42 @@ describe.runIf(process.env.TW_SECRET_KEY)("deployFromMetadata", () => { entrypoint: ENTRYPOINT_ADDRESS_v0_6, }, }); - const [tx1Result, tx2Result] = await Promise.all([ + const [tx1Result, tx2Result, tx3Result] = await Promise.all([ simulateTransaction({ transaction: tx1 }), simulateTransaction({ transaction: tx2 }), + simulateTransaction({ transaction: tx3 }), + ]); + expect(tx1Result === tx2Result).toBe(true); + expect(tx1Result !== tx3Result).toBe(true); + }); + + it("computed address and deployed address should match", async () => { + const computedPromise = computePublishedContractAddress({ + chain: FORKED_ETHEREUM_CHAIN, + client: TEST_CLIENT, + contractId: "AccountFactory", + constructorParams: { + defaultAdmin: TEST_ACCOUNT_A.address, + entrypoint: ENTRYPOINT_ADDRESS_v0_6, + }, + salt: keccakId("some-salt"), + }); + const tx = prepareDeterministicDeployTransaction({ + chain: FORKED_ETHEREUM_CHAIN, + client: TEST_CLIENT, + contractId: "AccountFactory", + constructorParams: { + defaultAdmin: TEST_ACCOUNT_A.address, + entrypoint: ENTRYPOINT_ADDRESS_v0_6, + }, + salt: keccakId("some-salt"), + }); + + const [computed, txResult] = await Promise.all([ + computedPromise, + simulateTransaction({ transaction: tx }), ]); - expect(tx1Result !== tx2Result).toBe(true); + expect(computed === txResult).toBe(true); }); // TODO: Replace these tests' live contracts with mocks it("should deploy a published contract with no constructor", async () => { From 22a95a083aaf94dd36e26a1e340fba11a6217fb7 Mon Sep 17 00:00:00 2001 From: Yash Date: Mon, 17 Feb 2025 22:28:56 +0530 Subject: [PATCH 4/4] for zk --- .../deployment/zksync/zkDeployDeterministic.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/thirdweb/src/contract/deployment/zksync/zkDeployDeterministic.ts b/packages/thirdweb/src/contract/deployment/zksync/zkDeployDeterministic.ts index 16e52933435..9524590d20b 100644 --- a/packages/thirdweb/src/contract/deployment/zksync/zkDeployDeterministic.ts +++ b/packages/thirdweb/src/contract/deployment/zksync/zkDeployDeterministic.ts @@ -13,7 +13,11 @@ import { } from "../../../utils/any-evm/zksync/constants.js"; import { isContractDeployed } from "../../../utils/bytecode/is-contract-deployed.js"; import { ensureBytecodePrefix } from "../../../utils/bytecode/prefix.js"; -import { type Hex, uint8ArrayToHex } from "../../../utils/encoding/hex.js"; +import { + type Hex, + isHex, + uint8ArrayToHex, +} from "../../../utils/encoding/hex.js"; import type { ClientAndChainAndAccount } from "../../../utils/types.js"; import { getContract } from "../../contract.js"; import { zkDeployContract } from "./zkDeployContract.js"; @@ -89,7 +93,11 @@ export async function zkDeployContractDeterministic( abi: parseAbi(singletonFactoryAbi), }); - const salt = options?.salt ? keccakId(options.salt) : keccakId("thirdweb"); + const salt = options?.salt + ? isHex(options.salt) && options.salt.length === 66 + ? options.salt + : keccakId(options.salt) + : keccakId("thirdweb"); await sendAndConfirmTransaction({ account: options.account,