mirror of
https://github.com/interep-project/contracts.git
synced 2026-04-17 03:00:51 -04:00
feat: create function to remove identity commitments
This commit is contained in:
@@ -17,13 +17,23 @@ contract Groups is OwnableUpgradeable {
|
||||
/// @param provider: The provider of the group.
|
||||
/// @param name: The name of the group.
|
||||
/// @param identityCommitment: The new identity commitment.
|
||||
/// @param index: The index of the identity commitment in the tree.
|
||||
/// @param root: The new root hash of the tree.
|
||||
event NewIdentityCommitment(
|
||||
bytes32 indexed provider,
|
||||
bytes32 indexed name,
|
||||
uint256 identityCommitment,
|
||||
uint256 index,
|
||||
uint256 root
|
||||
);
|
||||
|
||||
/// @dev Emitted when a new identity commitment is deleted.
|
||||
/// @param provider: The provider of the group.
|
||||
/// @param name: The name of the group.
|
||||
/// @param identityCommitment: The new identity commitment.
|
||||
/// @param root: The new root hash of the tree.
|
||||
event DeleteIdentityCommitment(
|
||||
bytes32 indexed provider,
|
||||
bytes32 indexed name,
|
||||
uint256 identityCommitment,
|
||||
uint256 root
|
||||
);
|
||||
|
||||
@@ -37,26 +47,29 @@ contract Groups is OwnableUpgradeable {
|
||||
__Ownable_init();
|
||||
}
|
||||
|
||||
/// @dev ...
|
||||
/// @dev Creates a new group by initializing the associated Merkle tree.
|
||||
/// @param provider: The provider of the group.
|
||||
/// @param name: The name of the group.
|
||||
/// @param depth: Depth of the tree.
|
||||
function createOwnableGroup(
|
||||
/// @param admin: Admin of the group.
|
||||
function createGroup(
|
||||
bytes32 provider,
|
||||
bytes32 name,
|
||||
uint8 depth,
|
||||
address admin
|
||||
) external {
|
||||
createGroup(provider, name, depth);
|
||||
|
||||
require(admin != owner(), "Groups: group admin cannot be the contract owner");
|
||||
|
||||
) external onlyOwner {
|
||||
bytes32 groupId = getGroupId(provider, name);
|
||||
|
||||
require(groups[groupId].depth == 0, "Groups: group already exists");
|
||||
|
||||
groups[groupId].init(depth, 0);
|
||||
|
||||
groupAdmins[groupId] = admin;
|
||||
|
||||
emit NewGroup(provider, name, depth);
|
||||
}
|
||||
|
||||
/// @dev ...
|
||||
/// @dev Batch function to add multiple identity commitments.
|
||||
/// @param provider: The provider of the group.
|
||||
/// @param names: The names of the group.
|
||||
/// @param identityCommitments: Identity commitments.
|
||||
@@ -72,7 +85,7 @@ contract Groups is OwnableUpgradeable {
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Gets a group provider and a group name and returns the last root hash of the group.
|
||||
/// @dev Returns the last root hash of the group.
|
||||
/// @return The root hash.
|
||||
function getRoot(bytes32 provider, bytes32 name) external view returns (uint256) {
|
||||
bytes32 groupId = getGroupId(provider, name);
|
||||
@@ -80,7 +93,7 @@ contract Groups is OwnableUpgradeable {
|
||||
return groups[groupId].root;
|
||||
}
|
||||
|
||||
/// @dev Gets a group provider and a group name and returns the size of the group.
|
||||
/// @dev Returns the size of the group.
|
||||
/// @return The root hash.
|
||||
function getSize(bytes32 provider, bytes32 name) external view returns (uint256) {
|
||||
bytes32 groupId = getGroupId(provider, name);
|
||||
@@ -88,25 +101,7 @@ contract Groups is OwnableUpgradeable {
|
||||
return groups[groupId].numberOfLeaves;
|
||||
}
|
||||
|
||||
/// @dev ...
|
||||
/// @param provider: The provider of the group.
|
||||
/// @param name: The name of the group.
|
||||
/// @param depth: Depth of the tree.
|
||||
function createGroup(
|
||||
bytes32 provider,
|
||||
bytes32 name,
|
||||
uint8 depth
|
||||
) public onlyOwner {
|
||||
bytes32 groupId = getGroupId(provider, name);
|
||||
|
||||
require(groups[groupId].depth == 0, "Groups: group already exists");
|
||||
|
||||
groups[groupId].init(depth, 0);
|
||||
|
||||
emit NewGroup(provider, name, depth);
|
||||
}
|
||||
|
||||
/// @dev ...
|
||||
/// @dev Adds an identity commitment to an existing group.
|
||||
/// @param provider: The provider of the group.
|
||||
/// @param name: The name of the group.
|
||||
/// @param identityCommitment: The new identity commitment.
|
||||
@@ -117,23 +112,40 @@ contract Groups is OwnableUpgradeable {
|
||||
) public {
|
||||
bytes32 groupId = getGroupId(provider, name);
|
||||
|
||||
require(
|
||||
(owner() == _msgSender() && groupAdmins[groupId] == address(0)) || groupAdmins[groupId] == _msgSender(),
|
||||
"Groups: caller is not the contract owner or the group admin"
|
||||
);
|
||||
require(groups[groupId].depth != 0, "Groups: group does not exist");
|
||||
require(groupAdmins[groupId] == _msgSender(), "Groups: caller is not the group admin");
|
||||
|
||||
groups[groupId].insert(identityCommitment);
|
||||
|
||||
emit NewIdentityCommitment(
|
||||
provider,
|
||||
name,
|
||||
identityCommitment,
|
||||
groups[groupId].numberOfLeaves - 1,
|
||||
groups[groupId].root
|
||||
);
|
||||
emit NewIdentityCommitment(provider, name, identityCommitment, groups[groupId].root);
|
||||
}
|
||||
|
||||
/// @dev Deletes an identity commitment from an existing group.
|
||||
/// @param provider: The provider of the group.
|
||||
/// @param name: The name of the group.
|
||||
/// @param identityCommitment: The new identity commitment.
|
||||
/// @param path: The new identity commitment.
|
||||
/// @param siblingNodes: The new identity commitment.
|
||||
function deleteIdentityCommitment(
|
||||
bytes32 provider,
|
||||
bytes32 name,
|
||||
uint256 identityCommitment,
|
||||
uint8[] memory path,
|
||||
uint256[] memory siblingNodes
|
||||
) public {
|
||||
bytes32 groupId = getGroupId(provider, name);
|
||||
|
||||
require(groups[groupId].depth != 0, "Groups: group does not exist");
|
||||
require(groupAdmins[groupId] == _msgSender(), "Groups: caller is not the group admin");
|
||||
|
||||
groups[groupId].remove(identityCommitment, path, siblingNodes);
|
||||
|
||||
emit DeleteIdentityCommitment(provider, name, identityCommitment, groups[groupId].root);
|
||||
}
|
||||
|
||||
/// @dev Returns the group id.
|
||||
/// @param provider: The provider of the group.
|
||||
/// @param name: The name of the group.
|
||||
function getGroupId(bytes32 provider, bytes32 name) private pure returns (bytes32) {
|
||||
return keccak256(abi.encodePacked(provider, name));
|
||||
}
|
||||
|
||||
@@ -52,4 +52,60 @@ library IncrementalTree {
|
||||
self.root = hash;
|
||||
self.numberOfLeaves += 1;
|
||||
}
|
||||
|
||||
function remove(
|
||||
TreeData storage self,
|
||||
uint256 leaf,
|
||||
uint8[] memory path,
|
||||
uint256[] memory siblingNodes
|
||||
) public {
|
||||
require(verify(self, leaf, path, siblingNodes), "IncrementalTree: leaf is not part of the tree");
|
||||
|
||||
uint256 hash = self.zeroes[0];
|
||||
|
||||
for (uint8 i = 0; i < self.depth; i++) {
|
||||
if (path[i] % 2 == 0) {
|
||||
if (siblingNodes[i] == self.lastNodes[i][1]) {
|
||||
self.lastNodes[i][0] = hash;
|
||||
}
|
||||
|
||||
hash = Hash.poseidon([hash, siblingNodes[i]]);
|
||||
} else {
|
||||
if (siblingNodes[i] == self.lastNodes[i][0]) {
|
||||
self.lastNodes[i][1] = hash;
|
||||
}
|
||||
|
||||
hash = Hash.poseidon([siblingNodes[i], hash]);
|
||||
}
|
||||
}
|
||||
|
||||
self.root = hash;
|
||||
}
|
||||
|
||||
function verify(
|
||||
TreeData storage self,
|
||||
uint256 leaf,
|
||||
uint8[] memory path,
|
||||
uint256[] memory siblingNodes
|
||||
) private view returns (bool) {
|
||||
require(leaf < SNARK_SCALAR_FIELD, "IncrementalTree: leaf must be < SNARK_SCALAR_FIELD");
|
||||
require(
|
||||
path.length == self.depth && siblingNodes.length == self.depth,
|
||||
"IncrementalTree: length of path is not correct"
|
||||
);
|
||||
|
||||
uint256 hash = leaf;
|
||||
|
||||
for (uint8 i = 0; i < self.depth; i++) {
|
||||
require(siblingNodes[i] < SNARK_SCALAR_FIELD, "IncrementalTree: sibling node must be < SNARK_SCALAR_FIELD");
|
||||
|
||||
if (path[i] % 2 == 0) {
|
||||
hash = Hash.poseidon([hash, siblingNodes[i]]);
|
||||
} else {
|
||||
hash = Hash.poseidon([siblingNodes[i], hash]);
|
||||
}
|
||||
}
|
||||
|
||||
return hash == self.root;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +76,7 @@
|
||||
"hardhat": "^2.3.0",
|
||||
"hardhat-gas-reporter": "^1.0.4",
|
||||
"husky": "^6.0.0",
|
||||
"incrementalquintree": "^1.0.7",
|
||||
"lint-staged": "^11.0.0",
|
||||
"mocha": "^8.4.0",
|
||||
"prettier": "^2.3.0",
|
||||
|
||||
@@ -1,44 +1,39 @@
|
||||
import { expect } from "chai"
|
||||
import { Signer } from "ethers"
|
||||
import { buildPoseidon } from "circomlibjs"
|
||||
import { ethers, run } from "hardhat"
|
||||
import { IncrementalQuinTree } from "incrementalquintree"
|
||||
import { Groups } from "../typechain"
|
||||
|
||||
describe("Groups", () => {
|
||||
let contract: Groups
|
||||
let signers: Signer[]
|
||||
let accounts: string[]
|
||||
|
||||
const provider = ethers.utils.formatBytes32String("twitter")
|
||||
const name = ethers.utils.formatBytes32String("gold")
|
||||
const identityCommitment = BigInt(1)
|
||||
|
||||
before(async () => {
|
||||
contract = await run("deploy:groups", { logs: false })
|
||||
|
||||
signers = await run("accounts", { logs: false })
|
||||
accounts = await Promise.all(signers.map((signer: Signer) => signer.getAddress()))
|
||||
})
|
||||
|
||||
it("Should create a group", async () => {
|
||||
const fun = () => contract.createGroup(provider, name, 16)
|
||||
const fun = () => contract.createGroup(provider, name, 16, accounts[0])
|
||||
|
||||
await expect(fun()).to.emit(contract, "NewGroup").withArgs(provider, name, 16)
|
||||
})
|
||||
|
||||
it("Should not create a group with an existing id", async () => {
|
||||
const fun = () => contract.createGroup(provider, name, 16)
|
||||
const fun = () => contract.createGroup(provider, name, 16, accounts[0])
|
||||
|
||||
await expect(fun()).to.be.revertedWith("Groups: group already exists")
|
||||
})
|
||||
|
||||
it("Should create an ownable group", async () => {
|
||||
const dao = ethers.utils.formatBytes32String("DAO")
|
||||
const name = ethers.utils.formatBytes32String("HelloWorld")
|
||||
const admin = await signers[1].getAddress()
|
||||
|
||||
const fun = () => contract.createOwnableGroup(dao, name, 16, admin)
|
||||
|
||||
await expect(fun()).to.emit(contract, "NewGroup").withArgs(dao, name, 16)
|
||||
})
|
||||
|
||||
it("Should not add an identity commitment if the group does not exist", async () => {
|
||||
const identityCommitment = BigInt(2)
|
||||
const otherName = ethers.utils.formatBytes32String("silver")
|
||||
|
||||
const fun = () => contract.addIdentityCommitment(provider, otherName, identityCommitment)
|
||||
@@ -46,20 +41,15 @@ describe("Groups", () => {
|
||||
await expect(fun()).to.be.revertedWith("Groups: group does not exist")
|
||||
})
|
||||
|
||||
it("Should not add an identity commitment if the caller is not the contract owner or the group admin", async () => {
|
||||
it("Should not add an identity commitment if the caller is not the group admin", async () => {
|
||||
const identityCommitment = BigInt(2)
|
||||
const otherName = ethers.utils.formatBytes32String("silver")
|
||||
|
||||
const fun = () => contract.connect(signers[2]).addIdentityCommitment(provider, otherName, identityCommitment)
|
||||
const fun = () => contract.connect(signers[1]).addIdentityCommitment(provider, name, identityCommitment)
|
||||
|
||||
await expect(fun()).to.be.revertedWith("Groups: caller is not the contract owner or the group admin")
|
||||
await expect(fun()).to.be.revertedWith("Groups: caller is not the group admin")
|
||||
})
|
||||
|
||||
it("Should add an identity commitment in a group", async () => {
|
||||
const identityCommitment = BigInt(
|
||||
"2825646560483793878176284075509449079260676404272675066033690163469311186662"
|
||||
)
|
||||
|
||||
const fun = () => contract.addIdentityCommitment(provider, name, identityCommitment)
|
||||
|
||||
await expect(fun())
|
||||
@@ -68,21 +58,10 @@ describe("Groups", () => {
|
||||
provider,
|
||||
name,
|
||||
identityCommitment,
|
||||
0,
|
||||
"13636421308146043413489220009267735248703575391714290025204419877115892930915"
|
||||
"16211261537006706331557500769845541584780950636316907182067421710925347020533"
|
||||
)
|
||||
})
|
||||
|
||||
it("Should add an identity commitment in a ownable group", async () => {
|
||||
const provider = ethers.utils.formatBytes32String("DAO")
|
||||
const name = ethers.utils.formatBytes32String("HelloWorld")
|
||||
const identityCommitment = BigInt(2)
|
||||
|
||||
const fun = () => contract.connect(signers[1]).addIdentityCommitment(provider, name, identityCommitment)
|
||||
|
||||
await expect(fun()).to.emit(contract, "NewIdentityCommitment")
|
||||
})
|
||||
|
||||
it("Should throw an error if the length of the array parameters is not the same", async () => {
|
||||
const names = ["gold", "silver", "bronze"].map(ethers.utils.formatBytes32String)
|
||||
const identityCommitments = [1, 2].map(BigInt)
|
||||
@@ -96,11 +75,35 @@ describe("Groups", () => {
|
||||
const names = ["gold", "silver", "bronze"].map(ethers.utils.formatBytes32String)
|
||||
const identityCommitments = [1, 2, 3].map(BigInt)
|
||||
|
||||
await contract.createGroup(provider, names[1], 16)
|
||||
await contract.createGroup(provider, names[2], 16)
|
||||
await contract.createGroup(provider, names[1], 16, accounts[0])
|
||||
await contract.createGroup(provider, names[2], 16, accounts[0])
|
||||
|
||||
const fun = () => contract.batchAddIdentityCommitment(provider, names, identityCommitments)
|
||||
|
||||
await expect(fun()).to.emit(contract, "NewIdentityCommitment")
|
||||
})
|
||||
|
||||
it("Should remove an identity commitment in a group", async () => {
|
||||
const name = ethers.utils.formatBytes32String("RM")
|
||||
const poseidon = await buildPoseidon()
|
||||
const tree = new IncrementalQuinTree(16, 0, 2, (inputs: BigInt[]) => poseidon.F.toObject(poseidon(inputs)))
|
||||
|
||||
tree.insert(BigInt(1))
|
||||
tree.insert(BigInt(2))
|
||||
tree.insert(BigInt(3))
|
||||
|
||||
await contract.createGroup(provider, name, 16, accounts[0])
|
||||
await contract.addIdentityCommitment(provider, name, BigInt(1))
|
||||
await contract.addIdentityCommitment(provider, name, BigInt(2))
|
||||
await contract.addIdentityCommitment(provider, name, BigInt(3))
|
||||
|
||||
tree.update(1, 0)
|
||||
|
||||
const path = tree.genMerklePath(1)
|
||||
const siblingNodes = path.pathElements.map((e: BigInt[]) => e[0])
|
||||
|
||||
const fun = () => contract.deleteIdentityCommitment(provider, name, BigInt(2), path.indices, siblingNodes)
|
||||
|
||||
await expect(fun()).to.emit(contract, "DeleteIdentityCommitment").withArgs(provider, name, BigInt(2), path.root)
|
||||
})
|
||||
})
|
||||
|
||||
47
types/incrementalquintree/index.d.ts
vendored
Normal file
47
types/incrementalquintree/index.d.ts
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
/** Declaration file generated by dts-gen */
|
||||
|
||||
declare module "incrementalquintree" {
|
||||
export class IncrementalQuinTree {
|
||||
constructor(_depth: any, _zeroValue: any, _leavesPerNode: any, _hashFunc: any)
|
||||
|
||||
copy(): any
|
||||
|
||||
equals(t: any): any
|
||||
|
||||
genMerklePath(_index: any): any
|
||||
|
||||
genMerkleSubrootPath(_startIndex: any, _endIndex: any): any
|
||||
|
||||
getLeaf(_index: any): any
|
||||
|
||||
hash(_leaves: any): any
|
||||
|
||||
insert(_value: any): void
|
||||
|
||||
update(_index: any, _value: any): void
|
||||
|
||||
static verifyMerklePath(_proof: any, _hashFunc: any): any
|
||||
}
|
||||
|
||||
export class MultiIncrementalQuinTree {
|
||||
constructor(_depth: any, _zeroValue: any, _leavesPerNode: any, _hashFunc: any)
|
||||
|
||||
copy(): any
|
||||
|
||||
equals(t: any): any
|
||||
|
||||
genMerklePath(_absoluteIndex: any): any
|
||||
|
||||
genMerkleSubrootPath(_absoluteStartIndex: any, _absoluteEndIndex: any): any
|
||||
|
||||
getLeaf(_index: any): any
|
||||
|
||||
hash(_leaves: any): any
|
||||
|
||||
insert(_value: any): void
|
||||
|
||||
update(_absoluteIndex: any, _value: any): void
|
||||
|
||||
static verifyMerklePath(_proof: any, _hashFunc: any): any
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user