mirror of
https://github.com/selfxyz/self.git
synced 2026-02-19 02:24:25 -05:00
* refactor: generate scope for SelfVerificationRoot upon deploment Utilise Poseidon to generate the scope for the deploying contract instead of relying on utilizing the Scope Generator tool on the frontend and calling a function that inherits the _setScope function * style: use explicit import for PoseidonT3 * fix: link Poseidon library in TestSelfVerificationRoot deployments * fix: Use same logic in SelfVerificationRoot as in hashEndpointWithScope * refactor: use hardcoded PoseidonT3 addresses for Celo Mainnet + Sepolia Also allowed functionality for testing environments which have a fresh deploy each time they are spun up, and which now utilize the testSetScope function for tests relying on TestSelfVerificationRoot * style: change setTestScope to setGenerateScope for clarity * refactor: Move logic out of SelfVerificationRoot into util files * chore: update version * fix: sepolia chain id * fmt --------- Co-authored-by: ayman <aymanshaik1015@gmail.com>
239 lines
9.6 KiB
TypeScript
239 lines
9.6 KiB
TypeScript
import { expect } from "chai";
|
|
import { ethers } from "hardhat";
|
|
import { TestSelfVerificationRoot } from "../../typechain-types";
|
|
import { stringToBigInt, bigIntToString, hashEndpointWithScope } from "@selfxyz/common/utils/scope";
|
|
|
|
describe("SelfVerificationRoot - Automatic Scope Generation", () => {
|
|
let testContract: TestSelfVerificationRoot;
|
|
let mockHubAddress: string;
|
|
let poseidonT3Address: string;
|
|
|
|
before(async () => {
|
|
const [signer] = await ethers.getSigners();
|
|
mockHubAddress = signer.address;
|
|
|
|
// Deploy PoseidonT3 library for testing
|
|
console.log("📚 Deploying PoseidonT3 library for testing...");
|
|
const PoseidonT3Factory = await ethers.getContractFactory("PoseidonT3");
|
|
const poseidonT3 = await PoseidonT3Factory.deploy();
|
|
await poseidonT3.waitForDeployment();
|
|
poseidonT3Address = await poseidonT3.getAddress();
|
|
|
|
console.log(`✅ PoseidonT3 deployed at: ${poseidonT3Address}`);
|
|
});
|
|
|
|
describe("Constructor Scope Generation", () => {
|
|
it("should have the scope set correctly, after contract deployment", async () => {
|
|
const scopeSeed = "test-scope-seed";
|
|
|
|
// Deploy the test contract
|
|
const TestContractFactory = await ethers.getContractFactory("TestSelfVerificationRoot");
|
|
testContract = await TestContractFactory.deploy(mockHubAddress, scopeSeed);
|
|
await testContract.waitForDeployment();
|
|
|
|
// Setup the scope manually using testGenerateScope (as this is a local dev network)
|
|
await testContract.testGenerateScope(poseidonT3Address, scopeSeed);
|
|
|
|
// Get the deployed contract address
|
|
const contractAddress = await testContract.getAddress();
|
|
|
|
// Get the actual scope from the contract
|
|
const actualScope = await testContract.scope();
|
|
|
|
console.log(`Contract Address: ${contractAddress}`);
|
|
console.log(`Scope Seed: "${scopeSeed}"`);
|
|
console.log(`Generated Scope: ${actualScope.toString()}`);
|
|
|
|
// Calculate expected scope using hashEndpointWithScope (use lowercase to match Solidity)
|
|
const expectedScope = BigInt(hashEndpointWithScope(contractAddress.toLowerCase(), scopeSeed));
|
|
console.log(`Expected Scope: ${expectedScope.toString()}`);
|
|
|
|
// Verify they match
|
|
expect(actualScope.toString()).to.equal(expectedScope.toString());
|
|
});
|
|
|
|
it("should generate different scopes for different scope seeds", async () => {
|
|
const scopeSeed1 = "scope-seed-1";
|
|
const scopeSeed2 = "scope-seed-2";
|
|
|
|
// Deploy two contracts with different scope seeds
|
|
const TestContractFactory = await ethers.getContractFactory("TestSelfVerificationRoot");
|
|
|
|
const contract1 = await TestContractFactory.deploy(mockHubAddress, scopeSeed1);
|
|
const contract2 = await TestContractFactory.deploy(mockHubAddress, scopeSeed2);
|
|
|
|
await contract1.waitForDeployment();
|
|
await contract2.waitForDeployment();
|
|
|
|
// Set scopes using testGenerateScope
|
|
await contract1.testGenerateScope(poseidonT3Address, scopeSeed1);
|
|
await contract2.testGenerateScope(poseidonT3Address, scopeSeed2);
|
|
|
|
const scope1 = await contract1.scope();
|
|
const scope2 = await contract2.scope();
|
|
|
|
// Should be different
|
|
expect(scope1).to.not.equal(scope2);
|
|
console.log(`Scope 1 (${scopeSeed1}): ${scope1.toString()}`);
|
|
console.log(`Scope 2 (${scopeSeed2}): ${scope2.toString()}`);
|
|
});
|
|
|
|
it("should generate different scopes for same scope seed but different addresses", async () => {
|
|
const scopeSeed = "same-scope-seed";
|
|
|
|
// Deploy two contracts with same scope seed (they'll have different addresses)
|
|
const TestContractFactory = await ethers.getContractFactory("TestSelfVerificationRoot");
|
|
|
|
const contract1 = await TestContractFactory.deploy(mockHubAddress, scopeSeed);
|
|
const contract2 = await TestContractFactory.deploy(mockHubAddress, scopeSeed);
|
|
|
|
await contract1.waitForDeployment();
|
|
await contract2.waitForDeployment();
|
|
|
|
// Set scopes using testGenerateScope
|
|
await contract1.testGenerateScope(poseidonT3Address, scopeSeed);
|
|
await contract2.testGenerateScope(poseidonT3Address, scopeSeed);
|
|
|
|
const scope1 = await contract1.scope();
|
|
const scope2 = await contract2.scope();
|
|
|
|
// Should be different due to different contract addresses
|
|
expect(scope1).to.not.equal(scope2);
|
|
|
|
const addr1 = await contract1.getAddress();
|
|
const addr2 = await contract2.getAddress();
|
|
console.log(`Contract 1 (${addr1}): ${scope1.toString()}`);
|
|
console.log(`Contract 2 (${addr2}): ${scope2.toString()}`);
|
|
});
|
|
|
|
it("should generate scope automatically without manual scope value", async () => {
|
|
const scopeSeed = "test-scope";
|
|
|
|
const TestContractFactory = await ethers.getContractFactory("TestSelfVerificationRoot");
|
|
testContract = await TestContractFactory.deploy(mockHubAddress, scopeSeed);
|
|
await testContract.waitForDeployment();
|
|
|
|
// Set scope using testGenerateScope
|
|
await testContract.testGenerateScope(poseidonT3Address, scopeSeed);
|
|
|
|
const actualScope = await testContract.scope();
|
|
|
|
// Should equal the generated scope
|
|
const contractAddress = await testContract.getAddress();
|
|
console.log(`Contract Address: ${contractAddress}`);
|
|
console.log(`Scope Seed: "${scopeSeed}"`);
|
|
|
|
// Debug: Let's trace the frontend logic step by step
|
|
console.log(
|
|
`Frontend hashEndpointWithScope result: ${hashEndpointWithScope(contractAddress.toLowerCase(), scopeSeed)}`,
|
|
);
|
|
|
|
const expectedScope = BigInt(hashEndpointWithScope(contractAddress.toLowerCase(), scopeSeed));
|
|
console.log(`Generated Scope: ${actualScope.toString()}`);
|
|
console.log(`Expected Scope: ${expectedScope.toString()}`);
|
|
|
|
expect(actualScope.toString()).to.equal(expectedScope.toString());
|
|
});
|
|
|
|
it("should handle various scope seed strings correctly", async () => {
|
|
const testCases = [
|
|
"simple",
|
|
"with-dashes",
|
|
"with_underscores",
|
|
"MiXeD-CaSe_123",
|
|
"symbols!@#$%",
|
|
"exactly-31-characters-in-length", // 31 chars (max)
|
|
"", // empty string
|
|
"a", // single character
|
|
];
|
|
|
|
for (const scopeSeed of testCases) {
|
|
const TestContractFactory = await ethers.getContractFactory("TestSelfVerificationRoot");
|
|
const contract = await TestContractFactory.deploy(mockHubAddress, scopeSeed);
|
|
await contract.waitForDeployment();
|
|
|
|
// Set scope using testGenerateScope
|
|
await contract.testGenerateScope(poseidonT3Address, scopeSeed);
|
|
|
|
const actualScope = await contract.scope();
|
|
|
|
// Calculate expected scope
|
|
const contractAddress = await contract.getAddress();
|
|
const expectedScope = BigInt(hashEndpointWithScope(contractAddress.toLowerCase(), scopeSeed));
|
|
|
|
expect(actualScope.toString()).to.equal(expectedScope.toString());
|
|
console.log(`Scope seed: "${scopeSeed}" -> Scope: ${actualScope.toString()}`);
|
|
}
|
|
});
|
|
|
|
it("should produce known expected value for specific test case", async () => {
|
|
// This test ensures our implementation matches the expected behavior
|
|
// If this fails, it means our Solidity implementation differs from frontend
|
|
const scopeSeed = "test-scope";
|
|
|
|
const TestContractFactory = await ethers.getContractFactory("TestSelfVerificationRoot");
|
|
const contract = await TestContractFactory.deploy(mockHubAddress, scopeSeed);
|
|
await contract.waitForDeployment();
|
|
|
|
// Set scope using testGenerateScope
|
|
await contract.testGenerateScope(poseidonT3Address, scopeSeed);
|
|
|
|
const contractAddress = await contract.getAddress();
|
|
const actualScope = await contract.scope();
|
|
|
|
// Calculate expected scope using hashEndpointWithScope (use lowercase to match Solidity)
|
|
const expectedScope = BigInt(hashEndpointWithScope(contractAddress.toLowerCase(), scopeSeed));
|
|
|
|
// This is the critical test - Solidity must match frontend exactly
|
|
expect(actualScope.toString()).to.equal(expectedScope.toString());
|
|
|
|
console.log(`\n=== KNOWN VALUE TEST ===`);
|
|
console.log(`Contract Address: ${contractAddress}`);
|
|
console.log(`Scope Seed: "${scopeSeed}"`);
|
|
console.log(`Expected Scope: ${expectedScope.toString()}`);
|
|
console.log(`Actual Scope: ${actualScope.toString()}`);
|
|
console.log(`Match: ${actualScope.toString() === expectedScope.toString()}`);
|
|
});
|
|
});
|
|
|
|
describe("String to BigInt Conversion", () => {
|
|
it("should convert strings to BigInt correctly (round-trip test)", async () => {
|
|
const testCases = [
|
|
"hello-world",
|
|
"test123",
|
|
"UPPERCASE",
|
|
"mixed_CASE_123",
|
|
"symbols!@#$%",
|
|
"short",
|
|
"",
|
|
"a",
|
|
"12345",
|
|
"exactly-31-characters-in-length", // 31 chars (max)
|
|
];
|
|
|
|
for (const str of testCases) {
|
|
const bigIntValue = stringToBigInt(str);
|
|
const roundTrip = bigIntToString(bigIntValue);
|
|
expect(roundTrip).to.equal(str);
|
|
console.log(`"${str}" -> ${bigIntValue.toString()} -> "${roundTrip}"`);
|
|
}
|
|
});
|
|
|
|
it("should handle edge cases correctly", async () => {
|
|
// Empty string
|
|
expect(stringToBigInt("")).to.equal(0n);
|
|
|
|
// Single character
|
|
expect(stringToBigInt("A")).to.equal(65n); // ASCII value of 'A'
|
|
|
|
// Two characters
|
|
expect(stringToBigInt("AB")).to.equal((65n << 8n) | 66n); // A=65, B=66
|
|
});
|
|
|
|
it("should throw error for strings exceeding 31 bytes", async () => {
|
|
const longString = "this-string-is-definitely-longer-than-31-bytes-and-should-fail";
|
|
expect(() => stringToBigInt(longString)).to.throw("Resulting BigInt exceeds maximum size of 31 bytes");
|
|
});
|
|
});
|
|
});
|