Skip to content

Commit b182302

Browse files
authored
[SDK] bytes32 salt for deterministic deployment (#6274)
1 parent 942d20a commit b182302

File tree

7 files changed

+76
-9
lines changed

7 files changed

+76
-9
lines changed

.changeset/popular-ligers-taste.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"thirdweb": patch
3+
---
4+
5+
bytes32 salt for deterministic deployment

packages/thirdweb/src/contract/deployment/deploy-deterministic.test.ts

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import {
77
import { TEST_CLIENT } from "../../../test/src/test-clients.js";
88
import { TEST_ACCOUNT_A } from "../../../test/src/test-wallets.js";
99
import { simulateTransaction } from "../../transaction/actions/simulate.js";
10+
import { computePublishedContractAddress } from "../../utils/any-evm/compute-published-contract-address.js";
11+
import { keccakId } from "../../utils/any-evm/keccak-id.js";
1012
import { ENTRYPOINT_ADDRESS_v0_6 } from "../../wallets/smart/lib/constants.js";
1113
import { prepareDeterministicDeployTransaction } from "./deploy-deterministic.js";
1214

@@ -51,6 +53,16 @@ describe.runIf(process.env.TW_SECRET_KEY)("deployFromMetadata", () => {
5153
salt: "some-salt",
5254
});
5355
const tx2 = prepareDeterministicDeployTransaction({
56+
chain: FORKED_ETHEREUM_CHAIN,
57+
client: TEST_CLIENT,
58+
contractId: "AccountFactory",
59+
constructorParams: {
60+
defaultAdmin: TEST_ACCOUNT_A.address,
61+
entrypoint: ENTRYPOINT_ADDRESS_v0_6,
62+
},
63+
salt: keccakId("some-salt"),
64+
});
65+
const tx3 = prepareDeterministicDeployTransaction({
5466
chain: FORKED_OPTIMISM_CHAIN,
5567
client: TEST_CLIENT,
5668
contractId: "AccountFactory",
@@ -59,11 +71,42 @@ describe.runIf(process.env.TW_SECRET_KEY)("deployFromMetadata", () => {
5971
entrypoint: ENTRYPOINT_ADDRESS_v0_6,
6072
},
6173
});
62-
const [tx1Result, tx2Result] = await Promise.all([
74+
const [tx1Result, tx2Result, tx3Result] = await Promise.all([
6375
simulateTransaction({ transaction: tx1 }),
6476
simulateTransaction({ transaction: tx2 }),
77+
simulateTransaction({ transaction: tx3 }),
78+
]);
79+
expect(tx1Result === tx2Result).toBe(true);
80+
expect(tx1Result !== tx3Result).toBe(true);
81+
});
82+
83+
it("computed address and deployed address should match", async () => {
84+
const computedPromise = computePublishedContractAddress({
85+
chain: FORKED_ETHEREUM_CHAIN,
86+
client: TEST_CLIENT,
87+
contractId: "AccountFactory",
88+
constructorParams: {
89+
defaultAdmin: TEST_ACCOUNT_A.address,
90+
entrypoint: ENTRYPOINT_ADDRESS_v0_6,
91+
},
92+
salt: keccakId("some-salt"),
93+
});
94+
const tx = prepareDeterministicDeployTransaction({
95+
chain: FORKED_ETHEREUM_CHAIN,
96+
client: TEST_CLIENT,
97+
contractId: "AccountFactory",
98+
constructorParams: {
99+
defaultAdmin: TEST_ACCOUNT_A.address,
100+
entrypoint: ENTRYPOINT_ADDRESS_v0_6,
101+
},
102+
salt: keccakId("some-salt"),
103+
});
104+
105+
const [computed, txResult] = await Promise.all([
106+
computedPromise,
107+
simulateTransaction({ transaction: tx }),
65108
]);
66-
expect(tx1Result !== tx2Result).toBe(true);
109+
expect(computed === txResult).toBe(true);
67110
});
68111
// TODO: Replace these tests' live contracts with mocks
69112
it("should deploy a published contract with no constructor", async () => {

packages/thirdweb/src/contract/deployment/zksync/zkDeployDeterministic.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ import {
1313
} from "../../../utils/any-evm/zksync/constants.js";
1414
import { isContractDeployed } from "../../../utils/bytecode/is-contract-deployed.js";
1515
import { ensureBytecodePrefix } from "../../../utils/bytecode/prefix.js";
16-
import { type Hex, uint8ArrayToHex } from "../../../utils/encoding/hex.js";
16+
import {
17+
type Hex,
18+
isHex,
19+
uint8ArrayToHex,
20+
} from "../../../utils/encoding/hex.js";
1721
import type { ClientAndChainAndAccount } from "../../../utils/types.js";
1822
import { getContract } from "../../contract.js";
1923
import { zkDeployContract } from "./zkDeployContract.js";
@@ -89,7 +93,11 @@ export async function zkDeployContractDeterministic(
8993
abi: parseAbi(singletonFactoryAbi),
9094
});
9195

92-
const salt = options?.salt ? keccakId(options.salt) : keccakId("thirdweb");
96+
const salt = options?.salt
97+
? isHex(options.salt) && options.salt.length === 66
98+
? options.salt
99+
: keccakId(options.salt)
100+
: keccakId("thirdweb");
93101

94102
await sendAndConfirmTransaction({
95103
account: options.account,

packages/thirdweb/src/utils/any-evm/compute-deployment-address.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { type Hex, encodePacked } from "viem";
22
import { getAddress } from "../address.js";
33
import { ensureBytecodePrefix } from "../bytecode/prefix.js";
4+
import { isHex } from "../encoding/hex.js";
45
import { keccak256 } from "../hashing/keccak256.js";
56
import { getSaltHash } from "./get-salt-hash.js";
67
import { keccakId } from "./keccak-id.js";
@@ -33,7 +34,9 @@ export function computeDeploymentAddress(
3334
) {
3435
const bytecode = ensureBytecodePrefix(options.bytecode);
3536
const saltHash = options.salt
36-
? keccakId(options.salt)
37+
? isHex(options.salt) && options.salt.length === 66
38+
? options.salt
39+
: keccakId(options.salt)
3740
: getSaltHash(bytecode);
3841

3942
// 1. create init bytecode hash with contract's bytecode and encoded args

packages/thirdweb/src/utils/any-evm/compute-published-contract-deploy-info.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,5 +96,6 @@ export async function computeDeploymentInfoFromBytecode(args: {
9696
initBytecodeWithsalt,
9797
encodedArgs,
9898
create2FactoryAddress,
99+
salt,
99100
};
100101
}

packages/thirdweb/src/utils/any-evm/get-init-bytecode-with-salt.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { encodePacked } from "viem/utils";
22
import { ensureBytecodePrefix } from "../bytecode/prefix.js";
3-
import { type Hex, uint8ArrayToHex } from "../encoding/hex.js";
3+
import { type Hex, isHex, uint8ArrayToHex } from "../encoding/hex.js";
44
import { getSaltHash } from "./get-salt-hash.js";
55
import { keccakId } from "./keccak-id.js";
66

@@ -29,8 +29,11 @@ export function getInitBytecodeWithSalt(
2929
options: GetInitiBytecodeWithSaltOptions,
3030
): Hex {
3131
const bytecode = ensureBytecodePrefix(options.bytecode);
32+
3233
const saltHash = options.salt
33-
? keccakId(options.salt)
34+
? isHex(options.salt) && options.salt.length === 66
35+
? options.salt
36+
: keccakId(options.salt)
3437
: getSaltHash(bytecode);
3538

3639
const encodedArgs =

packages/thirdweb/src/utils/any-evm/zksync/computeDeploymentAddress.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Address } from "../../address.js";
2-
import type { Hex } from "../../encoding/hex.js";
2+
import { type Hex, isHex } from "../../encoding/hex.js";
33
import { keccakId } from "../keccak-id.js";
44
import { create2Address } from "./create2Address.js";
55

@@ -13,7 +13,11 @@ type ComputeDeploymentAddressOptions = {
1313
export function computeDeploymentAddress(
1414
options: ComputeDeploymentAddressOptions,
1515
) {
16-
const saltHash = options.salt ? keccakId(options.salt) : keccakId("thirdweb");
16+
const saltHash = options.salt
17+
? isHex(options.salt) && options.salt.length === 66
18+
? options.salt
19+
: keccakId(options.salt)
20+
: keccakId("thirdweb");
1721

1822
return create2Address({
1923
sender: options.create2FactoryAddress,

0 commit comments

Comments
 (0)