o1js
Version:
TypeScript framework for zk-SNARKs and zkApps
240 lines (206 loc) • 7.43 kB
text/typescript
import {
AccountUpdate,
Bool,
fetchAccount,
Mina,
PrivateKey,
PublicKey,
Reducer,
SmartContract,
UInt32,
UInt64,
} from 'o1js';
import { VotingApp, VotingAppParams } from './factory.js';
import { Member, MyMerkleWitness } from './member.js';
import { OffchainStorage } from './off-chain-storage.js';
import { ParticipantPreconditions, ElectionPreconditions } from './preconditions.js';
import { getResults, vote } from './voting-lib.js';
const Berkeley = Mina.Network({
mina: 'https://api.minascan.io/node/devnet/v1/graphql',
archive: 'https://api.minascan.io/archive/devnet/v1/graphql',
});
Mina.setActiveInstance(Berkeley);
let feePayerKey = PrivateKey.random();
let feePayerAddress = feePayerKey.toPublicKey();
let voterKey = PrivateKey.random();
let candidateKey = PrivateKey.random();
let votingKey = PrivateKey.random();
console.log('waiting for accounts to receive funds...');
await Mina.faucet(feePayerAddress);
console.log('funds received');
console.log(`using the following addressed:
feePayer: ${feePayerAddress.toBase58()}
voting manager contract: ${votingKey.toPublicKey().toBase58()}
candidate membership contract: ${candidateKey.toPublicKey().toBase58()}
voter membership contract: ${voterKey.toPublicKey().toBase58()}`);
let params: VotingAppParams = {
candidatePreconditions: new ParticipantPreconditions(
UInt64.from(0),
UInt64.from(100_000_000_000)
),
voterPreconditions: new ParticipantPreconditions(UInt64.from(0), UInt64.from(100_000_000_000)),
electionPreconditions: new ElectionPreconditions(UInt32.from(0), UInt32.MAXINT(), Bool(false)),
voterKey,
candidateKey,
votingKey,
doProofs: false,
};
// we are using pre-funded voters here
const members = [
PublicKey.fromBase58('B62qqzhd5U54JafhR4CB8NLWQM8PRfiCZ4TuoTT5UQHzGwdR2f5RLnK'),
PublicKey.fromBase58('B62qnScMYfgSUWwtzB1r6fB8i23YFXgA25rzcSXVCtYVfUxLHkMLr3G'),
];
let storage = {
votesStore: new OffchainStorage<Member>(3),
candidatesStore: new OffchainStorage<Member>(3),
votersStore: new OffchainStorage<Member>(3),
};
console.log('building contracts');
let contracts = await VotingApp(params);
console.log('deploying set of 3 contracts');
let tx = await Mina.transaction(
{
sender: feePayerAddress,
fee: 10_000_000,
memo: 'Deploying contracts',
},
async () => {
AccountUpdate.fundNewAccount(feePayerAddress, 3);
await contracts.voting.deploy();
contracts.voting.committedVotes.set(storage.votesStore.getRoot());
contracts.voting.accumulatedVotes.set(Reducer.initialActionState);
await contracts.candidateContract.deploy();
contracts.candidateContract.committedMembers.set(storage.candidatesStore.getRoot());
contracts.candidateContract.accumulatedMembers.set(Reducer.initialActionState);
await contracts.voterContract.deploy();
contracts.voterContract.committedMembers.set(storage.votersStore.getRoot());
contracts.voterContract.accumulatedMembers.set(Reducer.initialActionState);
}
);
await tx.prove();
await (
await tx.sign([feePayerKey, params.votingKey, params.candidateKey, params.voterKey]).send()
).wait();
console.log('successfully deployed contracts');
await fetchAllAccounts();
console.log('registering one voter');
tx = await Mina.transaction(
{
sender: feePayerAddress,
fee: 10_000_000,
memo: 'Registering a voter',
},
async () => {
let m = registerMember(0n, Member.from(members[0], UInt64.from(150)), storage.votersStore);
await contracts.voting.voterRegistration(m);
}
);
await tx.prove();
await (await tx.sign([feePayerKey]).send()).wait();
console.log('voter registered');
await fetchAllAccounts();
console.log('registering one candidate');
tx = await Mina.transaction(
{
sender: feePayerAddress,
fee: 10_000_000,
memo: 'Registering a candidate',
},
async () => {
let m = registerMember(0n, Member.from(members[1], UInt64.from(150)), storage.candidatesStore);
await contracts.voting.candidateRegistration(m);
}
);
await tx.prove();
await (await tx.sign([feePayerKey]).send()).wait();
console.log('candidate registered');
// we have to wait a few seconds before continuing, otherwise we might not get the actions from the archive, we if continue too fast
await new Promise((resolve) => setTimeout(resolve, 20000));
await fetchAllAccounts();
console.log('approving registrations');
tx = await Mina.transaction(
{
sender: feePayerAddress,
fee: 10_000_000,
memo: 'Approving registrations',
},
async () => {
await contracts.voting.approveRegistrations();
}
);
await tx.prove();
await (await tx.sign([feePayerKey]).send()).wait();
console.log('registrations approved');
await fetchAllAccounts();
console.log('voting for a candidate');
tx = await Mina.transaction(
{ sender: feePayerAddress, fee: 10_000_000, memo: 'Casting vote' },
async () => {
let currentCandidate = storage.candidatesStore.get(0n)!;
currentCandidate.witness = new MyMerkleWitness(storage.candidatesStore.getWitness(0n));
currentCandidate.votesWitness = new MyMerkleWitness(storage.votesStore.getWitness(0n));
let v = storage.votersStore.get(0n)!;
v.witness = new MyMerkleWitness(storage.votersStore.getWitness(0n));
console.log(v.witness.calculateRoot(v.getHash()).toString());
console.log(contracts.voting.committedVotes.get().toString());
await contracts.voting.vote(currentCandidate, v);
}
);
await tx.prove();
await (await tx.sign([feePayerKey]).send()).wait();
vote(0n, storage.votesStore, storage.candidatesStore);
console.log('voted for a candidate');
await new Promise((resolve) => setTimeout(resolve, 20000));
await fetchAllAccounts();
console.log('counting votes');
tx = await Mina.transaction(
{ sender: feePayerAddress, fee: 10_000_000, memo: 'Counting votes' },
async () => {
await contracts.voting.countVotes();
}
);
await tx.prove();
await (await tx.sign([feePayerKey]).send()).wait();
console.log('votes counted');
await new Promise((resolve) => setTimeout(resolve, 20000));
await fetchAllAccounts();
let results = getResults(contracts.voting, storage.votesStore);
if (results[members[1].toBase58()] !== 1) {
throw Error(
`Candidate ${members[1].toBase58()} should have one vote, but has ${
results[members[1].toBase58()]
} `
);
}
console.log('final result', results);
console.log('The following events were emitted during the voting process:');
await displayEvents(contracts.voting);
await displayEvents(contracts.candidateContract);
await displayEvents(contracts.voterContract);
async function displayEvents(contract: SmartContract) {
let events = await contract.fetchEvents();
console.log(
`events on ${contract.address.toBase58()}`,
events.map((e) => {
return { type: e.type, data: JSON.stringify(e.event) };
})
);
}
async function fetchAllAccounts() {
await Promise.all(
[
feePayerAddress,
params.voterKey.toPublicKey(),
params.candidateKey.toPublicKey(),
params.votingKey.toPublicKey(),
...members,
].map((publicKey) => fetchAccount({ publicKey }))
);
}
function registerMember(i: bigint, m: Member, store: OffchainStorage<Member>): Member {
// we will also have to keep track of new voters and candidates within our off-chain merkle tree
store.set(i, m); // setting voter 0n
// setting the merkle witness
m.witness = new MyMerkleWitness(store.getWitness(i));
return m;
}