Skip to content

Commit

Permalink
adding packed token version
Browse files Browse the repository at this point in the history
  • Loading branch information
45930 committed Nov 15, 2023
1 parent 8d8790e commit 686b07e
Show file tree
Hide file tree
Showing 4 changed files with 233 additions and 8 deletions.
118 changes: 118 additions & 0 deletions src/PackedTokensElection.t.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// import { Ballot, PartialBallot, PackedTokensElection } from './PackedTokensElection';
// import { AccountUpdate, Mina, PrivateKey, PublicKey, UInt32 } from 'o1js';

// describe("PackedTokensElection", () => {
// let privilegedAddress: PublicKey;
// let zkappAddress: PublicKey;
// let sender: PublicKey;
// let privilegedKey: PrivateKey;
// let zkappKey: PrivateKey;
// let senderKey: PrivateKey;
// let initialBalance = 10_000_000_000;
// let zkapp: PackedTokensElection;

// beforeAll(async () => {
// console.time('compile');
// await PackedTokensElection.compile();
// console.timeEnd('compile');
// });

// beforeEach(async () => {
// let Local = Mina.LocalBlockchain({ proofsEnabled: true });
// zkappKey = PrivateKey.random();
// zkappAddress = zkappKey.toPublicKey();
// zkapp = new PackedTokensElection(zkappAddress);
// Mina.setActiveInstance(Local);

// sender = Local.testAccounts[0].publicKey;
// senderKey = Local.testAccounts[0].privateKey;


// // a special account that is allowed to pull out half of the zkapp balance, once
// privilegedKey = PrivateKey.random();
// privilegedAddress = privilegedKey.toPublicKey();
// });

// describe("Votes in the PackedTokensElection", () => {
// beforeEach(async () => {
// let Local = Mina.LocalBlockchain({ proofsEnabled: true });
// zkappKey = PrivateKey.random();
// zkappAddress = zkappKey.toPublicKey();
// zkapp = new PackedTokensElection(zkappAddress);
// Mina.setActiveInstance(Local);

// sender = Local.testAccounts[0].publicKey;
// senderKey = Local.testAccounts[0].privateKey;


// // a special account that is allowed to pull out half of the zkapp balance, once
// privilegedKey = PrivateKey.random();
// privilegedAddress = privilegedKey.toPublicKey();

// let tx = await Mina.transaction(sender, () => {
// let senderUpdate = AccountUpdate.fundNewAccount(sender);
// senderUpdate.send({ to: zkappAddress, amount: initialBalance });
// zkapp.deploy({ zkappKey });
// });
// await tx.prove();
// await tx.sign([senderKey]).send();

// tx = await Mina.transaction(sender, () => {
// let senderUpdate = AccountUpdate.fundNewAccount(sender);
// zkapp.faucet(sender);
// const partialBallot1 = PartialBallot.fromBigInts([0n, 0n, 100n, 0n, 0n, 0n, 0n]);
// const partialBallot2 = PartialBallot.fromBigInts([0n, 0n, 0n, 0n, 0n, 0n, 10n]);
// const myVote = new Ballot({
// partial1: partialBallot1,
// partial2: partialBallot2
// })
// zkapp.castVote(myVote, UInt32.from(110));
// });
// await tx.prove();
// await tx.sign([senderKey]).send();
// });

// it("Updates the State", async () => {
// const tx2 = await Mina.transaction(sender, () => {
// zkapp.reduceVotes();
// })
// await tx2.prove();
// await tx2.sign([senderKey]).send();
// const zkappState = zkapp.ballot.get();
// const pb1 = zkappState.partial1;
// const pb2 = zkappState.partial2;
// expect(String(pb1.toBigInts())).toBe(String([0n, 0n, 100n, 0n, 0n, 0n, 0n]));
// expect(String(pb2.toBigInts())).toBe(String([0n, 0n, 0n, 0n, 0n, 0n, 10n]));
// });

// it("has the correct number of tokens remaining", async () => {
// const remainingVoteBalance = await Mina.getBalance(sender, zkapp.token.id);
// expect(remainingVoteBalance.toString()).toBe(String(50_000 - 110))
// });

// it("updates a second time", async () => {
// let tx = await Mina.transaction(sender, () => {
// const partialBallot1 = PartialBallot.fromBigInts([101n, 0n, 0n, 0n, 0n, 0n, 0n]);
// const partialBallot2 = PartialBallot.fromBigInts([0n, 0n, 0n, 0n, 20n, 0n, 40n]);
// const myVote = new Ballot({
// partial1: partialBallot1,
// partial2: partialBallot2
// })
// zkapp.castVote(myVote, UInt32.from(161));
// });
// await tx.prove();
// await tx.sign([senderKey]).send();

// const tx2 = await Mina.transaction(sender, () => {
// zkapp.reduceVotes();
// })
// await tx2.prove();
// await tx2.sign([senderKey]).send();
// const zkappState = zkapp.ballot.get();
// const pb1 = zkappState.partial1;
// const pb2 = zkappState.partial2;
// expect(String(pb1.toBigInts())).toBe(String([101n, 0n, 100n, 0n, 0n, 0n, 0n]));
// expect(String(pb2.toBigInts())).toBe(String([0n, 0n, 0n, 0n, 20n, 0n, 50n]));
// });
// });
// });
111 changes: 111 additions & 0 deletions src/PackedTokensElection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { SmartContract, state, State, method, UInt32, Struct, UInt64, PublicKey, fetchAccount, Mina } from 'o1js';
import { PackedUInt32Factory, MultiPackedStringFactory } from 'o1js-pack';

export class IpfsHash extends MultiPackedStringFactory(4) { }
export class PartialBallot extends PackedUInt32Factory() { }

export class Ballot extends Struct({
partial1: PartialBallot,
partial2: PartialBallot
}) { }

export class Elector extends PackedUInt32Factory(2) { }

const MAX_BALLOTS = 2;

// example for testing - vote targets must be addresses so they will be compatible with custom tokens
// We don't need their private keys since a vote recepient can't send or do anything with their votes
export const PROPOSAL_VOTE_ADDRESSES = [
'B62qjmHWm7NjMUodZRnSHDfPqKZsfyaL2YnyU3Yc5rLfRKLbCsowKtc',
'B62qr1DKZu7wSEs9nxt83JLTEQhBt2ivcpeuEy8yTy44bBSKYnbPWHm',
'B62qmi9Pv8D79Vu41A1kM7RPcb4NQQbscUovpmsMdkMD1oYDYRNokA7',
'B62qmXMX6mvsyt7cK7WaJyLpZZiNzbXM4TPjC3zxa8BohePJBW2KmJg',
'B62qm3aJ84KriBzCCzT1ZbzDd8vfsNxQAEHnrTdQB5aLArqiZJ8DqX1',
'B62qp3jka3ARjWvua5AEbanNMzJ9vvfkVzCUUnhAzrPTTycW1zUs54Z',
'B62qnATYY9cSEGDxR1jsEZ8X3R8FDJxq6VQw5uuCnnS2eA5TjFmwD7y',
'B62qjoRr6SzrdidQfxA3CBtW8mELty72FaZymWjw6s5tDfw6Sm8xx8g',
'B62qmg5ZeHQRSjUXAZ1NTQxxhu24VkMT7PJ5krNxukpWPaTE8NxBmLa',
'B62qmVuzkyEC1j3Z6S3PPcph2u5tt1HWSgojRPAUZBMtqqSNebGe5d9',
'B62qkNy4Eawd8QoNAQF913mzegmFdPXaaxpbGD4GJt4qjDGAzDoJqBs',
'B62qktuDV9p5rVrrm4d7pf47HEeDyPDmCBzXrYDSYU2tvG5EYMWdsmN',
'B62qog9E8ghfTFEUjTQLVgk3K6W8Ux3KojvpfKSaHtWjp2vuvE3fRfh',
'B62qjyL1xnycNLtNmJL43bCYEUUwz72q9pX1L3PGBhZp9YbJnKzCVny'
]

export class PackedTokensElection extends SmartContract {
@state(IpfsHash) electionDetailsIpfs = State<IpfsHash>();
@state(Ballot) ballot = State<Ballot>();

init() {
super.init();
this.electionDetailsIpfs.set(IpfsHash.fromString(''));
this.ballot.set({
partial1: PartialBallot.fromBigInts([0n, 0n, 0n, 0n, 0n, 0n, 0n]),
partial2: PartialBallot.fromBigInts([0n, 0n, 0n, 0n, 0n, 0n, 0n])
});
}

@method
faucet(toAddress: PublicKey) {
/**
* Create a new Elector with state:
* token_balance: 50_000,
* token_balance: 0,
*/
const elector = Elector.fromBigInts([50_000n, 0n]);
this.token.mint({
address: toAddress,
amount: UInt64.from(elector.packed)
});
}

@method
setElectionDetails(electionDetailsIpfs: IpfsHash) {
this.electionDetailsIpfs.getAndAssertEquals();
this.electionDetailsIpfs.assertEquals(IpfsHash.fromString(''));
this.electionDetailsIpfs.set(electionDetailsIpfs);
}

@method
castVote(vote: Ballot, amount: UInt32) {
const currentTokenBalance = Mina.getBalance(this.sender, this.tokenId);
const existingElector = Elector.unpack(currentTokenBalance.value);
existingElector[0].assertLessThan(UInt32.from(MAX_BALLOTS), `This user has already voted ${MAX_BALLOTS} times in this election`);
existingElector[1].assertGreaterThanOrEqual(amount, `Existing user balance: ${existingElector[1]} is not enough for requested vote amount: ${amount}`);

const unpackedVote1 = PartialBallot.unpack(vote.partial1.packed);
const unpackedVote2 = PartialBallot.unpack(vote.partial2.packed);
const ballot = this.ballot.getAndAssertEquals();
const unpackedPartialBallot1 = PartialBallot.unpack(ballot.partial1.packed);
const unpackedPartialBallot2 = PartialBallot.unpack(ballot.partial2.packed);

let voteSum = UInt32.from(0);
for (let i = 0; i < PartialBallot.l; i++) {
voteSum = voteSum.add(unpackedVote1[i]);
unpackedPartialBallot1[i] = unpackedPartialBallot1[i].add(unpackedVote1[i]);
this.token.mint({
address: PublicKey.fromBase58(PROPOSAL_VOTE_ADDRESSES[i]),
amount: UInt64.from(unpackedVote1[i])
});
}
for (let i = 0; i < PartialBallot.l; i++) {
voteSum = voteSum.add(unpackedVote2[i]);
unpackedPartialBallot2[i] = unpackedPartialBallot2[i].add(unpackedVote2[i]);
this.token.mint({
address: PublicKey.fromBase58(PROPOSAL_VOTE_ADDRESSES[i + PartialBallot.l]),
amount: UInt64.from(unpackedVote2[i])
});
}
voteSum.assertEquals(amount); // sum of votes must equal asserted amount (can vote for multiple options)
this.token.burn({
address: this.sender,
amount: currentTokenBalance
});

const newElector = Elector.fromUInt32s([existingElector[0].add(1), existingElector[1].sub(amount)]) // incr num_ballots and decr voting balance
this.token.mint({
address: this.sender,
amount: UInt64.from(newElector.packed)
});
}
}
8 changes: 2 additions & 6 deletions src/TokenElection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,13 @@ export class TokenElection extends SmartContract {
castVote(vote: Ballot, amount: UInt32) {
const unpackedVote1 = PartialBallot.unpack(vote.partial1.packed);
const unpackedVote2 = PartialBallot.unpack(vote.partial2.packed);
const ballot = this.ballot.getAndAssertEquals();
const unpackedPartialBallot1 = PartialBallot.unpack(ballot.partial1.packed);
const unpackedPartialBallot2 = PartialBallot.unpack(ballot.partial2.packed);

let voteSum = UInt32.from(0);
for (let i = 0; i < PartialBallot.l; i++) {
voteSum = voteSum.add(unpackedVote1[i]);
unpackedPartialBallot1[i] = unpackedPartialBallot1[i].add(unpackedVote1[i]);
}
for (let i = 0; i < PartialBallot.l; i++) {
voteSum = voteSum.add(unpackedVote2[i]);
unpackedPartialBallot2[i] = unpackedPartialBallot1[i].add(unpackedVote2[i]);
}
voteSum.assertEquals(amount); // sum of votes must equal asserted amount (can vote for multiple options)
this.token.burn({
Expand All @@ -68,8 +63,9 @@ export class TokenElection extends SmartContract {

@method
reduceVotes() {
this.ballot.getAndAssertEquals();
const actionState = this.actionState.getAndAssertEquals();
const ballot = this.ballot.getAndAssertEquals();

let pendingActions = this.reducer.getActions({
fromActionState: actionState,
});
Expand Down
4 changes: 2 additions & 2 deletions src/examples/ZkIgnite/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ You will need to have a funded account on the testnet and the ability to clone t
2. Install the dependencies and build the project `npm i && npm run build`
- run `zk config` and use 'berkeley' as your deploy alias
3. Access the vote details `node build/src/examples/ZKIgnite/getElectionDetails.js`
- When prompted for the contract, enter: B62qp3CPDr6VZTYMNuU4tNzgoSN52jg2ZbZdgARk883dv1YrRZ6CUsP
- When prompted for the contract, enter: B62qoMByycorTLENUAvV6Zr1gEfE5KERWSq1AanzPfc8dvKB8d1f3ka
4. Opt-in as an elector `node build/src/examples/ZKIgnite/joinElection.js`
- When prompted for the contract, enter: B62qp3CPDr6VZTYMNuU4tNzgoSN52jg2ZbZdgARk883dv1YrRZ6CUsP
- When prompted for the contract, enter: B62qoMByycorTLENUAvV6Zr1gEfE5KERWSq1AanzPfc8dvKB8d1f3ka
- You will need to sign this transaction, but in a more realistic scenario, Mina Foundation would decide who is eligible to be an elector.
5. Review the election details from step 3 and decide how you would like to vote. You may vote for as many projects as you like, as long as the total vote amount is less than 50,000, since that is the balance you have to work with.
6. Submit your votes: `node build/src/examples/ZKIgnite/submitVotes.js`
Expand Down

0 comments on commit 686b07e

Please sign in to comment.