Skip to content

Commit ced5f6a

Browse files
authored
chore(contracts): add function to deploy contracts deterministically (#816)
* chore(contracts): add function to deploy contracts deterministically re #766 * chore(contracts): return correct contract instance re #766
1 parent 1b046fd commit ced5f6a

File tree

2 files changed

+60
-26
lines changed

2 files changed

+60
-26
lines changed

packages/contracts/scripts/utils.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { SupportedNetwork, isSupportedNetwork } from "@semaphore-protocol/utils"
22
import { readFileSync, writeFileSync } from "fs"
3+
import { FactoryOptions } from "hardhat/types"
34

45
export type NetworkDeployedContracts = {
56
name: "Semaphore" | "SemaphoreVerifier" | "PoseidonT3"
@@ -14,6 +15,51 @@ export type DeployedContracts = {
1415

1516
const deployedContractsPath = "../utils/src/networks/deployed-contracts.json"
1617

18+
/**
19+
* Deploys contracts using a deterministic deployment proxy if
20+
* the network is supported, otherwise it deploys contract with
21+
* the factory as usual.
22+
* Part of this code may be moved to a separate Hardhat plugin in
23+
* the future.
24+
*/
25+
export async function deploy(
26+
ethers: any,
27+
contractName: string,
28+
network?: string,
29+
args: any[] = [],
30+
opts?: FactoryOptions
31+
): Promise<string> {
32+
const ContractFactory = await ethers.getContractFactory(contractName, opts)
33+
34+
// If the network is not a local one and it's supported, it deploys contracts deterministically.
35+
if (network && isSupportedNetwork(network)) {
36+
// Proxy address got from https://github.com/Arachnid/deterministic-deployment-proxy.
37+
const proxyAddress = "0x4e59b44847b379578588920ca78fbf26c0b4956c"
38+
// For the moment, salt will always be zero.
39+
const salt = `0x${"0".repeat(64)}`
40+
41+
const { bytecode, interface: abi } = ContractFactory
42+
const [signer] = await ethers.getSigners()
43+
44+
// If the contract has a constructor with arguments, they should be added to the bytecode/initcode.
45+
const encodedArgs = args ? abi.encodeDeploy(args).slice(2) : ""
46+
47+
await signer.sendTransaction({
48+
to: proxyAddress,
49+
data: `${salt}${bytecode.replace("0x", "")}${encodedArgs}`
50+
})
51+
52+
// Contract addresses can be calculated deterministically.
53+
return ethers.getCreate2Address(proxyAddress, salt, ethers.keccak256(bytecode + encodedArgs))
54+
}
55+
56+
const contract = await ContractFactory.deploy(...args)
57+
58+
await contract.waitForDeployment()
59+
60+
return contract.getAddress()
61+
}
62+
1763
export function getDeployedContracts(): DeployedContracts {
1864
return JSON.parse(readFileSync(deployedContractsPath, "utf8"))
1965
}

packages/contracts/tasks/deploy.ts

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { SupportedNetwork } from "@semaphore-protocol/utils"
22
import { task, types } from "hardhat/config"
3-
import { saveDeployedContracts } from "../scripts/utils"
3+
import { deploy, saveDeployedContracts } from "../scripts/utils"
44

55
task("deploy", "Deploy a Semaphore contract")
66
.addOptionalParam<boolean>("verifier", "Verifier contract address", undefined, types.string)
@@ -14,44 +14,32 @@ task("deploy", "Deploy a Semaphore contract")
1414
const startBlock = await ethers.provider.getBlockNumber()
1515

1616
if (!semaphoreVerifierAddress) {
17-
const VerifierFactory = await ethers.getContractFactory(`SemaphoreVerifier`)
18-
19-
const verifier = await VerifierFactory.deploy()
20-
21-
await verifier.waitForDeployment()
22-
23-
semaphoreVerifierAddress = await verifier.getAddress()
17+
semaphoreVerifierAddress = await deploy(ethers, "SemaphoreVerifier", hardhatArguments.network)
2418

2519
if (logs) {
2620
console.info(`SemaphoreVerifier contract has been deployed to: ${semaphoreVerifierAddress}`)
2721
}
2822
}
2923

3024
if (!poseidonT3Address) {
31-
const PoseidonT3Factory = await ethers.getContractFactory("PoseidonT3")
32-
33-
const poseidonT3 = await PoseidonT3Factory.deploy()
34-
35-
await poseidonT3.waitForDeployment()
36-
37-
poseidonT3Address = await poseidonT3.getAddress()
25+
poseidonT3Address = await deploy(ethers, "PoseidonT3", hardhatArguments.network)
3826

3927
if (logs) {
4028
console.info(`PoseidonT3 library has been deployed to: ${poseidonT3Address}`)
4129
}
4230
}
4331

44-
const SemaphoreFactory = await ethers.getContractFactory("Semaphore", {
45-
libraries: {
46-
PoseidonT3: poseidonT3Address
32+
const semaphoreAddress = await deploy(
33+
ethers,
34+
"Semaphore",
35+
hardhatArguments.network,
36+
[semaphoreVerifierAddress],
37+
{
38+
libraries: {
39+
PoseidonT3: poseidonT3Address
40+
}
4741
}
48-
})
49-
50-
const semaphore = await SemaphoreFactory.deploy(semaphoreVerifierAddress)
51-
52-
await semaphore.waitForDeployment()
53-
54-
const semaphoreAddress = await semaphore.getAddress()
42+
)
5543

5644
if (logs) {
5745
console.info(`Semaphore contract has been deployed to: ${semaphoreAddress}`)
@@ -79,7 +67,7 @@ task("deploy", "Deploy a Semaphore contract")
7967
)
8068

8169
return {
82-
semaphore,
70+
semaphore: await ethers.getContractAt("Semaphore", semaphoreAddress),
8371
verifierAddress: semaphoreVerifierAddress,
8472
poseidonAddress: poseidonT3Address
8573
}

0 commit comments

Comments
 (0)