refactor(utils): merge contract-utils and utils libraries

This commit is contained in:
cedoor
2023-02-26 16:25:42 +01:00
parent b3542e07a9
commit 5759176bed
24 changed files with 243 additions and 233 deletions

View File

@@ -1,7 +0,0 @@
{
"name": "@zk-groups/contract-utils",
"private": true,
"dependencies": {
"ethers": "^6.0.5"
}
}

View File

@@ -1,21 +0,0 @@
/* istanbul ignore file */
import ZKGroups from "contract-artifacts/ZKGroups.json"
import Semaphore from "contract-artifacts/Semaphore.json"
import { Contract } from "ethers"
import { getNetworkConfig } from "./networks"
import { ContractName } from "./types"
export default function getContractInstance(
contractName: ContractName
): Contract {
const network = getNetworkConfig()
switch (contractName) {
case "ZKGroups":
return new Contract(network.zkGroupsContract, ZKGroups.abi)
case "Semaphore":
return new Contract(network.semaphoreContract, Semaphore.abi)
default:
throw new TypeError(`'${contractName}' contract does not exist`)
}
}

View File

@@ -1,10 +0,0 @@
/* istanbul ignore file */
import { JsonRpcProvider } from "ethers"
import { getNetworkConfig } from "./networks"
import { Network } from "./types"
export default function getProvider(networkName: Network): JsonRpcProvider {
const network = getNetworkConfig(networkName)
const provider = new JsonRpcProvider(network.url)
return provider
}

View File

@@ -1,15 +0,0 @@
/* istanbul ignore file */
import { Signer, Wallet } from "ethers"
import getProvider from "./getProvider"
import { Network } from "./types"
export default async function getSigner(networkName: Network): Promise<Signer> {
if (!process.env["BACKEND_PRIVATE_KEY"]) {
throw new Error("Please set your BACKEND_PRIVATE_KEY in your .env file")
}
const provider = getProvider(networkName)
// TODO: move ethers from an Hardhat account to the backend account.
return new Wallet(process.env["BACKEND_PRIVATE_KEY"], provider)
}

View File

@@ -1,15 +0,0 @@
import * as zkGroups from "./zkGroups"
import * as semaphore from "./semaphore"
import NETWORKS from "./networks"
import getSigner from "./getSigner"
import getProvider from "./getProvider"
import getContractInstance from "./getContractInstance"
export {
getSigner,
getProvider,
getContractInstance,
semaphore,
zkGroups,
NETWORKS
}

View File

@@ -1,50 +0,0 @@
import { Network } from "./types"
type NetworkConfigParams = {
url: string
chainId: number
semaphoreContract: string
zkGroupsContract: string
}
const infuraApiKey = process.env.INFURA_API_KEY
const NETWORKS: { [K in Network]?: NetworkConfigParams } = {
localhost: {
url: "http://127.0.0.1:8545",
chainId: 31337,
semaphoreContract: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512",
zkGroupsContract: "0x5FbDB2315678afecb367f032d93F642f64180aa3"
},
goerli: {
url: `https://goerli.infura.io/v3/${infuraApiKey}`,
chainId: 5,
semaphoreContract: "0x89490c95eD199D980Cdb4FF8Bac9977EDb41A7E7",
zkGroupsContract: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0"
},
arbitrum: {
url: "https://arb1.arbitrum.io/rpc",
chainId: 42161,
semaphoreContract: "",
zkGroupsContract: ""
}
}
export function getNetworkConfig(
networkName = process.env.NX_DEFAULT_NETWORK as Network
) {
if (networkName !== "localhost" && !process.env["INFURA_API_KEY"]) {
throw new Error("Please set your INFURA_API_KEY in your .env file")
}
const network = NETWORKS[networkName]
if (!network) {
throw new Error(
"'networkName' not provided or invalid value set for 'NX_DEFAULT_NETWORK' env variable"
)
}
return network
}
export default NETWORKS

View File

@@ -1,74 +0,0 @@
/* istanbul ignore file */
import { Signer, TransactionReceipt } from "ethers"
import { formatBytes32String } from "ethers/lib/utils"
import getContractInstance from "./getContractInstance"
export function formatUint256String(text: string): BigInt {
return BigInt(formatBytes32String(text))
}
export async function createGroup(
signer: Signer,
groupName: string,
merkleTreeDepth: number
): Promise<any> {
const admin = signer
const groupId = formatUint256String(groupName)
const contractInstance = getContractInstance("Semaphore").connect(admin)
const transaction = await contractInstance[
"createGroup(uint256,uint256,address)"
](groupId, merkleTreeDepth, await admin.getAddress())
return transaction.wait(1)
}
export async function updateGroupAdmin(
signer: Signer,
groupName: string,
newAdmin: string
): Promise<TransactionReceipt> {
const admin = signer
const groupId = formatUint256String(groupName)
const contractInstance = getContractInstance("Semaphore").connect(admin)
const transaction = await contractInstance[
"updateGroupAdmin(uint256,address)"
](groupId, newAdmin)
return transaction.wait(1)
}
export async function addMember(
signer: Signer,
groupName: string,
member: string
): Promise<TransactionReceipt> {
const admin = signer
const groupId = formatUint256String(groupName)
const contractInstance = getContractInstance("Semaphore").connect(admin)
const transaction = await contractInstance["addMember(uint256,uint256)"](
groupId,
member
)
return transaction.wait(1)
}
export async function addMembers(
signer: Signer,
groupName: string,
members: string[]
): Promise<TransactionReceipt> {
const admin = signer
const groupId = formatUint256String(groupName)
const contractInstance = getContractInstance("Semaphore").connect(admin)
const transaction = await contractInstance["addMembers(uint256,uint256[])"](
groupId,
members
)
return transaction.wait(1)
}

View File

@@ -1,11 +0,0 @@
// Supported networks: https://docs.ethers.org/v5/api/providers/api-providers/#InfuraProvider
export type Network =
| "homestead"
| "goerli"
| "sepolia"
| "arbitrum"
| "matic"
| "optimism"
| "localhost"
export type ContractName = "ZKGroups" | "Semaphore"

View File

@@ -1,24 +0,0 @@
/* istanbul ignore file */
import { TransactionReceipt } from "ethers"
import getContractInstance from "./getContractInstance"
import getSigner from "./getSigner"
import { Network } from "./types"
export type Group = {
id: BigInt
fingerprint: BigInt
}
export async function updateGroups(
groups: Group[],
network: Network = process.env.NX_DEFAULT_NETWORK as Network
): Promise<TransactionReceipt> {
const signer = await getSigner(network)
const contractInstance = getContractInstance("ZKGroups").connect(
signer
) as any
const transaction = await contractInstance.updateGroups(groups)
return transaction.wait(1)
}

View File

@@ -1,4 +0,0 @@
{
"extends": "../../tsconfig.json",
"include": ["src"]
}

View File

@@ -29,6 +29,7 @@
"access": "public"
},
"devDependencies": {
"@rollup/plugin-json": "^6.0.0",
"@rollup/plugin-typescript": "^11.0.0",
"rimraf": "^4.1.2",
"rollup": "^3.17.2",
@@ -37,6 +38,10 @@
},
"dependencies": {
"@ethersproject/address": "^5.7.0",
"@ethersproject/contracts": "^5.7.0",
"@ethersproject/providers": "^5.7.0",
"@ethersproject/strings": "^5.7.0",
"@ethersproject/wallet": "^5.7.0",
"axios": "^1.3.3"
}
}

View File

@@ -1,4 +1,5 @@
import typescript from "@rollup/plugin-typescript"
import json from "@rollup/plugin-json"
import * as fs from "fs"
import cleanup from "rollup-plugin-cleanup"
@@ -20,6 +21,7 @@ export default {
],
external: Object.keys(pkg.dependencies),
plugins: [
json(),
typescript({
tsconfig: "./tsconfig.build.json"
}),

View File

@@ -0,0 +1,21 @@
import { ContractName, Network } from "./types"
const CONTRACT_ADDRESSES: { [K in Network]: { [Y in ContractName]: string } } =
{
localhost: {
Semaphore: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512",
ZKGroups: "0x5FbDB2315678afecb367f032d93F642f64180aa3"
},
goerli: {
Semaphore: "0x89490c95eD199D980Cdb4FF8Bac9977EDb41A7E7",
ZKGroups: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0"
}
}
export function getContractAddresses(networkName: Network): {
[Y in ContractName]: string
} {
return CONTRACT_ADDRESSES[networkName]
}
export default CONTRACT_ADDRESSES

View File

@@ -0,0 +1,28 @@
import { Contract } from "@ethersproject/contracts"
import { getContractAddresses } from "./contract-addresses"
import { abi as SemaphoreABI } from "./contract-artifacts/Semaphore.json"
import { abi as ZKGroupsABI } from "./contract-artifacts/ZKGroups.json"
import getProvider from "./getProvider"
import getWallet from "./getWallet"
import { ContractName, Network } from "./types"
export default function getContract(
contractName: ContractName,
network: Network,
privateKey?: string
): Contract {
const providerOrWallet = privateKey
? getWallet(privateKey, network)
: getProvider(network)
const contractAddress = getContractAddresses(network)[contractName]
switch (contractName) {
case "ZKGroups":
return new Contract(contractAddress, ZKGroupsABI, providerOrWallet)
case "Semaphore":
return new Contract(contractAddress, SemaphoreABI, providerOrWallet)
default:
throw new TypeError(`'${contractName}' contract does not exist`)
}
}

View File

@@ -0,0 +1,16 @@
import { InfuraProvider, JsonRpcProvider } from "@ethersproject/providers"
import { Network } from "./types"
export default function getProvider(
network: Network,
apiKey?: string
): JsonRpcProvider {
switch (network) {
case "localhost":
return new JsonRpcProvider()
case "goerli":
return new InfuraProvider(network, apiKey)
default:
throw new TypeError(`'${network}' network is not supported`)
}
}

View File

@@ -0,0 +1,73 @@
import { Contract, ContractReceipt } from "@ethersproject/contracts"
import { formatBytes32String } from "@ethersproject/strings"
import getContract from "./getContract"
import { Network } from "./types"
export class SemaphoreContract {
private contract: Contract
constructor(contract: Contract) {
this.contract = contract
}
async createGroup(
groupName: string,
merkleTreeDepth: number,
admin: string
): Promise<ContractReceipt> {
const groupId = BigInt(formatBytes32String(groupName))
const transaction = await this.contract.createGroup(
groupId,
merkleTreeDepth,
admin
)
return transaction.wait(1)
}
async updateGroupAdmin(
groupName: string,
newAdmin: string
): Promise<ContractReceipt> {
const groupId = BigInt(formatBytes32String(groupName))
const transaction = await this.contract.updateGroupAdmin(
groupId,
newAdmin
)
return transaction.wait(1)
}
async addMember(
groupName: string,
member: string
): Promise<ContractReceipt> {
const groupId = BigInt(formatBytes32String(groupName))
const transaction = await this.contract.addMember(groupId, member)
return transaction.wait(1)
}
async addMembers(
groupName: string,
members: string[]
): Promise<ContractReceipt> {
const groupId = BigInt(formatBytes32String(groupName))
const transaction = await this.contract.addMembers(groupId, members)
return transaction.wait(1)
}
}
export default function getSemaphoreContract(
network: Network,
privateKey: string
): SemaphoreContract {
const contract = getContract("Semaphore", network, privateKey)
return new SemaphoreContract(contract)
}

View File

@@ -0,0 +1,16 @@
import { Wallet } from "@ethersproject/wallet"
import getProvider from "./getProvider"
import { Network } from "./types"
export default function getWallet(
privateKey: string,
network?: Network
): Wallet {
if (network) {
const provider = getProvider(network)
return new Wallet(privateKey, provider)
}
return new Wallet(privateKey)
}

View File

@@ -0,0 +1,26 @@
import { Contract, ContractReceipt } from "@ethersproject/contracts"
import getContract from "./getContract"
import { Network, OnchainZKGroup } from "./types"
export class ZKGroupsContract {
private contract: Contract
constructor(contract: Contract) {
this.contract = contract
}
async updateGroups(groups: OnchainZKGroup[]): Promise<ContractReceipt> {
const transaction = await this.contract.updateGroups(groups)
return transaction.wait(1)
}
}
export default function getZKGroupsContract(
network: Network,
privateKey: string
): ZKGroupsContract {
const contract = getContract("ZKGroups", network, privateKey)
return new ZKGroupsContract(contract)
}

View File

@@ -0,0 +1,13 @@
import shortenAddress from "./shortenAddress"
describe("Utils", () => {
describe("# shortenAddress", () => {
it("Should shorten an Ethereum address", () => {
const address = shortenAddress(
"0x1234567890123456789012345678901234567890"
)
expect(address).toBe("0x1234...7890")
})
})
})

View File

@@ -1,4 +1,27 @@
import CONTRACT_ADDRESSES, { getContractAddresses } from "./contract-addresses"
import { abi as SemaphoreABI } from "./contract-artifacts/Semaphore.json"
import { abi as ZKGroupsABI } from "./contract-artifacts/ZKGroups.json"
import getContract from "./getContract"
import getProvider from "./getProvider"
import getSemaphoreContract, { SemaphoreContract } from "./getSemaphoreContract"
import getWallet from "./getWallet"
import getZKGroupsContract, { ZKGroupsContract } from "./getZKGroupsContract"
import request from "./request"
import shortenAddress from "./shortenAddress"
export { request, shortenAddress }
export * from "./types/index"
export {
request,
shortenAddress,
getProvider,
getContract,
getSemaphoreContract,
SemaphoreContract,
getZKGroupsContract,
ZKGroupsContract,
getWallet,
CONTRACT_ADDRESSES,
getContractAddresses,
SemaphoreABI,
ZKGroupsABI
}

View File

@@ -1,6 +1,5 @@
import axios, { AxiosRequestConfig, AxiosResponse } from "axios"
/* istanbul ignore next */
export default async function request(
url: string,
config?: AxiosRequestConfig

View File

@@ -0,0 +1,19 @@
// Supported networks: https://docs.ethers.org/v5/api/providers/api-providers/#InfuraProvider
export type Network =
| "localhost"
// | "homestead"
| "goerli"
// | "sepolia"
// | "arbitrum"
// | "arbitrum-goerli"
// | "matic"
// | "maticmum"
// | "optimism"
// | "optimism-goerli"
export type ContractName = "ZKGroups" | "Semaphore"
export type OnchainZKGroup = {
id: BigInt
fingerprint: BigInt
}