diff --git a/contracts/GitcoinPassportDecoder.sol b/contracts/GitcoinPassportDecoder.sol index 59b0a1d..28f4e68 100644 --- a/contracts/GitcoinPassportDecoder.sol +++ b/contracts/GitcoinPassportDecoder.sol @@ -27,6 +27,9 @@ contract GitcoinPassportDecoder is // Mapping of the current version to provider arrays mapping(uint32 => string[]) public providerVersions; + // Mapping of previously stored providers + mapping(string => uint256) public savedProviders; + // Current version number uint32 public currentVersion; @@ -36,6 +39,9 @@ contract GitcoinPassportDecoder is // Passport attestation schema UID bytes32 public schemaUID; + // Errors + error ProviderAlreadyExists(string provider); + function initialize() public initializer { __Ownable_init(); __Pausable_init(); @@ -77,10 +83,21 @@ contract GitcoinPassportDecoder is /** * @dev Adds a new provider to the end of the providerVersions mapping - * @param provider Name of individual provider + * @param providers provider name */ - function addProvider(string memory provider) public onlyOwner { - providerVersions[currentVersion].push(provider); + function addProviders(string[] memory providers) public onlyOwner { + for (uint256 i = 0; i < providers.length; ) { + if (savedProviders[providers[i]] == 1) { + revert ProviderAlreadyExists(providers[i]); + } + + providerVersions[currentVersion].push(providers[i]); + savedProviders[providers[i]] = 1; + + unchecked { + ++i; + } + } } /** @@ -172,7 +189,7 @@ contract GitcoinPassportDecoder is uint256 mappedProvidersIndex = i * 256 + j; - if (mappedProvidersIndex < mappedProviders.length) { + if (mappedProvidersIndex > mappedProviders.length) { break; } @@ -194,6 +211,7 @@ contract GitcoinPassportDecoder is hashIndex += 1; } + unchecked { bit <<= 1; ++j; @@ -208,4 +226,3 @@ contract GitcoinPassportDecoder is return passportMemoryArray; } } - diff --git a/deployments/abi/GitcoinPassportDecoder.json b/deployments/abi/GitcoinPassportDecoder.json new file mode 100644 index 0000000..5d4e9ea --- /dev/null +++ b/deployments/abi/GitcoinPassportDecoder.json @@ -0,0 +1,94 @@ +{ + "0xe704": [ + "event AdminChanged(address previousAdmin, address newAdmin)", + "event BeaconUpgraded(address indexed beacon)", + "event Initialized(uint8 version)", + "event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)", + "event Paused(address account)", + "event Unpaused(address account)", + "event Upgraded(address indexed implementation)", + "function addProvider(string provider)", + "function createNewVersion(string[] providerNames)", + "function currentVersion() view returns (uint32)", + "function getAttestation(bytes32 attestationUID) view returns (tuple(bytes32 uid, bytes32 schema, uint64 time, uint64 expirationTime, uint64 revocationTime, bytes32 refUID, address recipient, address attester, bool revocable, bytes data))", + "function getPassport(address userAddress) view returns (tuple(string provider, bytes32 hash, uint64 issuanceDate, uint64 expirationDate)[])", + "function gitcoinResolver() view returns (address)", + "function initialize()", + "function owner() view returns (address)", + "function pause()", + "function paused() view returns (bool)", + "function providerVersions(uint32, uint256) view returns (string)", + "function proxiableUUID() view returns (bytes32)", + "function renounceOwnership()", + "function schemaUID() view returns (bytes32)", + "function setEASAddress(address _easContractAddress)", + "function setGitcoinResolver(address _gitcoinResolver)", + "function setSchemaUID(bytes32 _schemaUID)", + "function transferOwnership(address newOwner)", + "function unpause()", + "function upgradeTo(address newImplementation)", + "function upgradeToAndCall(address newImplementation, bytes data) payable" + ], + "0x1a4": [ + "event AdminChanged(address previousAdmin, address newAdmin)", + "event BeaconUpgraded(address indexed beacon)", + "event Initialized(uint8 version)", + "event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)", + "event Paused(address account)", + "event Unpaused(address account)", + "event Upgraded(address indexed implementation)", + "function addProvider(string provider)", + "function createNewVersion(string[] providerNames)", + "function currentVersion() view returns (uint32)", + "function getAttestation(bytes32 attestationUID) view returns (tuple(bytes32 uid, bytes32 schema, uint64 time, uint64 expirationTime, uint64 revocationTime, bytes32 refUID, address recipient, address attester, bool revocable, bytes data))", + "function getPassport(address userAddress) view returns (tuple(string provider, bytes32 hash, uint64 issuanceDate, uint64 expirationDate)[])", + "function gitcoinResolver() view returns (address)", + "function initialize()", + "function owner() view returns (address)", + "function pause()", + "function paused() view returns (bool)", + "function providerVersions(uint32, uint256) view returns (string)", + "function proxiableUUID() view returns (bytes32)", + "function renounceOwnership()", + "function schemaUID() view returns (bytes32)", + "function setEASAddress(address _easContractAddress)", + "function setGitcoinResolver(address _gitcoinResolver)", + "function setSchemaUID(bytes32 _schemaUID)", + "function transferOwnership(address newOwner)", + "function unpause()", + "function upgradeTo(address newImplementation)", + "function upgradeToAndCall(address newImplementation, bytes data) payable" + ], + "0x14a33": [ + "error ProviderAlreadyExists()", + "event AdminChanged(address previousAdmin, address newAdmin)", + "event BeaconUpgraded(address indexed beacon)", + "event Initialized(uint8 version)", + "event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)", + "event Paused(address account)", + "event Unpaused(address account)", + "event Upgraded(address indexed implementation)", + "function addProviders(string[] providers)", + "function createNewVersion(string[] providerNames)", + "function currentVersion() view returns (uint32)", + "function getAttestation(bytes32 attestationUID) view returns (tuple(bytes32 uid, bytes32 schema, uint64 time, uint64 expirationTime, uint64 revocationTime, bytes32 refUID, address recipient, address attester, bool revocable, bytes data))", + "function getPassport(address userAddress) view returns (tuple(string provider, bytes32 hash, uint64 issuanceDate, uint64 expirationDate)[])", + "function gitcoinResolver() view returns (address)", + "function initialize()", + "function owner() view returns (address)", + "function pause()", + "function paused() view returns (bool)", + "function providerVersions(uint32, uint256) view returns (string)", + "function proxiableUUID() view returns (bytes32)", + "function renounceOwnership()", + "function savedProviders(string) view returns (uint256)", + "function schemaUID() view returns (bytes32)", + "function setEASAddress(address _easContractAddress)", + "function setGitcoinResolver(address _gitcoinResolver)", + "function setSchemaUID(bytes32 _schemaUID)", + "function transferOwnership(address newOwner)", + "function unpause()", + "function upgradeTo(address newImplementation)", + "function upgradeToAndCall(address newImplementation, bytes data) payable" + ] +} \ No newline at end of file diff --git a/deployments/abi/GitcoinResolver.json b/deployments/abi/GitcoinResolver.json index 679b298..84559b1 100644 --- a/deployments/abi/GitcoinResolver.json +++ b/deployments/abi/GitcoinResolver.json @@ -2,7 +2,9 @@ "0x14a33": [ "error AccessDenied()", "error InsufficientValue()", + "error InvalidAttester()", "error InvalidEAS()", + "error NotAllowlisted()", "error NotPayable()", "event AdminChanged(address previousAdmin, address newAdmin)", "event BeaconUpgraded(address indexed beacon)", @@ -13,8 +15,10 @@ "event Upgraded(address indexed implementation)", "function _eas() view returns (address)", "function _gitcoinAttester() view returns (address)", - "function aNewPublicVairable() view returns (uint256)", + "function addToAllowlist(address addr)", + "function allowlist(address) view returns (bool)", "function attest(tuple(bytes32 uid, bytes32 schema, uint64 time, uint64 expirationTime, uint64 revocationTime, bytes32 refUID, address recipient, address attester, bool revocable, bytes data) attestation) payable returns (bool)", + "function getUserAttestation(address user, bytes32 schema) view returns (bytes32)", "function initialize(address eas, address gitcoinAttester)", "function isPayable() pure returns (bool)", "function multiAttest(tuple(bytes32 uid, bytes32 schema, uint64 time, uint64 expirationTime, uint64 revocationTime, bytes32 refUID, address recipient, address attester, bool revocable, bytes data)[] attestations, uint256[]) payable returns (bool)", @@ -23,6 +27,7 @@ "function pause()", "function paused() view returns (bool)", "function proxiableUUID() view returns (bytes32)", + "function removeFromAllowlist(address addr)", "function renounceOwnership()", "function revoke(tuple(bytes32 uid, bytes32 schema, uint64 time, uint64 expirationTime, uint64 revocationTime, bytes32 refUID, address recipient, address attester, bool revocable, bytes data) attestation) payable returns (bool)", "function transferOwnership(address newOwner)", diff --git a/deployments/abi/GitcoinVerifier.json b/deployments/abi/GitcoinVerifier.json index 42d7877..65773d7 100644 --- a/deployments/abi/GitcoinVerifier.json +++ b/deployments/abi/GitcoinVerifier.json @@ -1,5 +1,8 @@ { "0x14a33": [ + "error InsufficientFee()", + "error InvalidNonce()", + "error InvalidSignature()", "event AdminChanged(address previousAdmin, address newAdmin)", "event BeaconUpgraded(address indexed beacon)", "event Initialized(uint8 version)", diff --git a/deployments/onchainInfo.json b/deployments/onchainInfo.json index d3ae881..b5ab9da 100644 --- a/deployments/onchainInfo.json +++ b/deployments/onchainInfo.json @@ -7,21 +7,24 @@ "address": "0xAcfE09Fd03f7812F022FBf636700AdEA18Fd2A7A" }, "GitcoinResolver": { - "address": "0xA10606c17d94a4DBDc4dD804d1B88fF5030aeE94" + "address": "0xDCBD32496495aB389b72741Aa4aAAda6Ae817321" }, "GitcoinVerifier": { - "address": "0x5bC95C6e11520D25BE8c7bDf7AAc3E2eEAbD8564" + "address": "0xA6FB077602B5A9C13893ce5F50CD24B660cf717C" }, "GitcoinAttester": { - "address": "0x5bbbc733e12f50e6834c40a90066f2f9ffb820e0" + "address": "0xE018e14023a134Ebf4b48CE1ba2b10204209612e" }, "easSchemas": { "passport": { - "uid": "0x9d43e5c201404b9ab0913bcd6475c78e32d32a4d78233b2c309e9b6828f59e45" + "uid": "0x8787ce9766a61683acc9b94cb469e22ea21fc27ea24f6224fd5239efa3332302" }, "score": { - "uid": "0xf72ba57b5fb4c12a5f967574bc44a11efb8be76bcfbf47ced36e1afe226ba702" + "uid": "0x537b498843f447aea33d6a79595fa1a8137edfc82802d909d9f8f81d4bdf6f44" } + }, + "GitcoinPassportDecoder": { + "address": "0x21221bFCf485c1658470017EA3bD02f238d6e8d3" } }, "0xa": { @@ -131,4 +134,4 @@ "address": "0xCAa9E817f02486cE076560B77A86235Ef91c5d5D" } } -} +} \ No newline at end of file diff --git a/deployments/providerBitMapInfo.json b/deployments/providerBitMapInfo.json new file mode 100644 index 0000000..07a372a --- /dev/null +++ b/deployments/providerBitMapInfo.json @@ -0,0 +1 @@ +[{"bit":0,"index":0,"name":"SelfStakingBronze"},{"bit":1,"index":0,"name":"SelfStakingSilver"},{"bit":2,"index":0,"name":"SelfStakingGold"},{"bit":3,"index":0,"name":"CommunityStakingBronze"},{"bit":4,"index":0,"name":"CommunityStakingSilver"},{"bit":5,"index":0,"name":"CommunityStakingGold"},{"bit":6,"index":0,"name":"GitcoinContributorStatistics#numGrantsContributeToGte#1"},{"bit":7,"index":0,"name":"GitcoinContributorStatistics#numGrantsContributeToGte#10"},{"bit":8,"index":0,"name":"GitcoinContributorStatistics#numGrantsContributeToGte#25"},{"bit":9,"index":0,"name":"GitcoinContributorStatistics#numGrantsContributeToGte#100"},{"bit":10,"index":0,"name":"GitcoinContributorStatistics#totalContributionAmountGte#10"},{"bit":11,"index":0,"name":"GitcoinContributorStatistics#totalContributionAmountGte#100"},{"bit":12,"index":0,"name":"GitcoinContributorStatistics#totalContributionAmountGte#1000"},{"bit":13,"index":0,"name":"GitcoinContributorStatistics#numGr14ContributionsGte#1"},{"bit":14,"index":0,"name":"GitcoinContributorStatistics#numRoundsContributedToGte#1"},{"bit":15,"index":0,"name":"twitterAccountAgeGte#180"},{"bit":16,"index":0,"name":"twitterAccountAgeGte#365"},{"bit":17,"index":0,"name":"twitterAccountAgeGte#730"},{"bit":18,"index":0,"name":"Discord"},{"bit":19,"index":0,"name":"Google"},{"bit":20,"index":0,"name":"githubAccountCreationGte#90"},{"bit":21,"index":0,"name":"githubAccountCreationGte#180"},{"bit":22,"index":0,"name":"githubAccountCreationGte#365"},{"bit":23,"index":0,"name":"githubContributionActivityGte#30"},{"bit":24,"index":0,"name":"githubContributionActivityGte#60"},{"bit":25,"index":0,"name":"githubContributionActivityGte#120"},{"bit":26,"index":0,"name":"Facebook"},{"bit":27,"index":0,"name":"FacebookProfilePicture"},{"bit":28,"index":0,"name":"Linkedin"},{"bit":29,"index":0,"name":"Ens"},{"bit":30,"index":0,"name":"Brightid"},{"bit":31,"index":0,"name":"Poh"},{"bit":32,"index":0,"name":"ethPossessionsGte#1"},{"bit":33,"index":0,"name":"ethPossessionsGte#10"},{"bit":34,"index":0,"name":"ethPossessionsGte#32"},{"bit":35,"index":0,"name":"FirstEthTxnProvider"},{"bit":36,"index":0,"name":"EthGTEOneTxnProvider"},{"bit":37,"index":0,"name":"EthGasProvider"},{"bit":38,"index":0,"name":"SnapshotVotesProvider"},{"bit":39,"index":0,"name":"SnapshotProposalsProvider"},{"bit":40,"index":0,"name":"NFT"},{"bit":41,"index":0,"name":"ZkSync"},{"bit":42,"index":0,"name":"ZkSyncEra"},{"bit":43,"index":0,"name":"Lens"},{"bit":44,"index":0,"name":"GnosisSafe"},{"bit":45,"index":0,"name":"Coinbase"},{"bit":46,"index":0,"name":"GuildMember"},{"bit":47,"index":0,"name":"GuildAdmin"},{"bit":48,"index":0,"name":"GuildPassportMember"},{"bit":49,"index":0,"name":"Hypercerts"},{"bit":50,"index":0,"name":"PHIActivitySilver"},{"bit":51,"index":0,"name":"PHIActivityGold"},{"bit":52,"index":0,"name":"HolonymGovIdProvider"},{"bit":53,"index":0,"name":"IdenaState#Newbie"},{"bit":54,"index":0,"name":"IdenaState#Verified"},{"bit":55,"index":0,"name":"IdenaState#Human"},{"bit":56,"index":0,"name":"IdenaStake#1k"},{"bit":57,"index":0,"name":"IdenaStake#10k"},{"bit":58,"index":0,"name":"IdenaStake#100k"},{"bit":59,"index":0,"name":"IdenaAge#5"},{"bit":60,"index":0,"name":"IdenaAge#10"},{"bit":61,"index":0,"name":"CivicCaptchaPass"},{"bit":62,"index":0,"name":"CivicUniquenessPass"},{"bit":63,"index":0,"name":"CivicLivenessPass"},{"bit":64,"index":0,"name":"CyberProfilePremium"},{"bit":65,"index":0,"name":"CyberProfilePaid"},{"bit":66,"index":0,"name":"CyberProfileOrgMember"},{"bit":67,"index":0,"name":"GrantsStack3Projects"},{"bit":68,"index":0,"name":"GrantsStack5Projects"},{"bit":69,"index":0,"name":"GrantsStack7Projects"},{"bit":70,"index":0,"name":"GrantsStack2Programs"},{"bit":71,"index":0,"name":"GrantsStack4Programs"},{"bit":72,"index":0,"name":"GrantsStack6Programs"},{"bit":73,"index":0,"name":"TrustaLabs"}] \ No newline at end of file diff --git a/docs/03-new-deployment.md b/docs/03-new-deployment.md index 06cb839..157d3c6 100644 --- a/docs/03-new-deployment.md +++ b/docs/03-new-deployment.md @@ -7,13 +7,15 @@ This is the process to deploy these contracts to a new chain: 2. Run `initializeChainInfo.ts` and follow instructions 3. Run `deployVerifierAndAttester.ts` (this calls addVerifier and setEASAddress) 4. Run `deployResolver.ts` -5. Verify contracts, make sure everything looks good -6. Ensure `PASSPORT_MULTISIG_ADDRESS` is set in your .env, then run `transferOwnership.ts` -7. Create EAS schemas pointing to the new resolver, +5. Run `deployPassportDecoder.ts` +6. Run `setupDecoder.ts` +7. Verify contracts, make sure everything looks good +8. Ensure `PASSPORT_MULTISIG_ADDRESS` is set in your .env, then run `transferOwnership.ts` +9. Create EAS schemas pointing to the new resolver, add to `onChainInfo.json` -8. In the Passport app, copy over the new `deployments` directory and - configure `NEXT_PUBLIC_ACTIVE_ON_CHAIN_PASSPORT_CHAINIDS` - and `NEXT_PUBLIC_POSSIBLE_ON_CHAIN_PASSPORT_CHAINIDS` +10. In the Passport app, copy over the new `deployments` directory and + configure `NEXT_PUBLIC_ACTIVE_ON_CHAIN_PASSPORT_CHAINIDS` + and `NEXT_PUBLIC_POSSIBLE_ON_CHAIN_PASSPORT_CHAINIDS` ## Notes diff --git a/hardhat.config.ts b/hardhat.config.ts index c6d23d9..9f4935f 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -37,6 +37,14 @@ let config: HardhatUserConfig = { browserURL: "https://goerli.basescan.org/", }, }, + { + network: "linea-goerli", + chainId: 59140, + urls: { + apiURL: "https://api.lineascan.build/api", + browserURL: "https://goerli.lineascan.build/", + }, + }, ], }, gasReporter: { @@ -86,6 +94,14 @@ if (process.env.DEPLOYER_PRIVATE_KEY && process.env.DEPLOYER_ADDRESS) { from: process.env.DEPLOYER_ADDRESS as string, }; } + if (process.env.OP_GOERLI_PROVIDER_URL) { + config.networks["optimism-goerli"] = { + url: process.env.OP_GOERLI_PROVIDER_URL as string, + accounts: [process.env.DEPLOYER_PRIVATE_KEY as string], + chainId: 420, + from: process.env.DEPLOYER_ADDRESS as string, + }; + } } } @@ -114,6 +130,7 @@ if ( ) { if (config.networks) { config.networks["baseGoerli"] = { + gasPrice: 1000005, url: process.env.CB_PROVIDER_URL as string, accounts: [process.env.CB_PRIVATE_KEY as string], chainId: 84531, diff --git a/scripts/setupDecoder.ts b/scripts/setupDecoder.ts new file mode 100644 index 0000000..f9fee94 --- /dev/null +++ b/scripts/setupDecoder.ts @@ -0,0 +1,53 @@ +import hre, { ethers } from "hardhat"; +import { + confirmContinue, + assertEnvironment, + getResolverAddress, + getPassportDecoderAddress, + getEASAddress, + getThisChainInfo, +} from "./lib/utils"; +import { getSchemaUID } from "@ethereum-attestation-service/eas-sdk"; + +import newBitMap from "../deployments/providerBitMapInfo.json"; + +assertEnvironment(); + +export async function main() { + await confirmContinue({ + contract: "Add schema and bitmap information to GitcoinPassportDecoder", + network: hre.network.name, + chainId: hre.network.config.chainId, + }); + + const chainInfo = getThisChainInfo(); + + const GitcoinPassportDecoder = await ethers.getContractFactory( + "GitcoinPassportDecoder" + ); + const passportDecoder = GitcoinPassportDecoder.attach( + getPassportDecoderAddress() + ); + + const easAddress = getEASAddress(); + + await passportDecoder.setEASAddress(easAddress); + console.log(`✅ Set EAS address ${easAddress} on GitcoinPassportDecoder.`); + + await passportDecoder.setGitcoinResolver(getResolverAddress()); + console.log( + `✅ Set GitcoinResolver address ${getResolverAddress()} on GitcoinPassportDecoder.` + ); + + await passportDecoder.setSchemaUID(chainInfo.easSchemas.passport.uid); + console.log( + `✅ Set Passport SchemaUID to ${chainInfo.easSchemas.passport.uid} on GitcoinPassportDecoder.` + ); + + const providers = newBitMap.map((bit) => bit.name); + await passportDecoder.addProviders(providers); + + console.log(`✅ Added providers to GitcoinPassportDecoder.`); +} + +main(); diff --git a/test/GitcoinPassportDecoder.ts b/test/GitcoinPassportDecoder.ts index adcd337..e277ffa 100644 --- a/test/GitcoinPassportDecoder.ts +++ b/test/GitcoinPassportDecoder.ts @@ -13,26 +13,43 @@ import { } from "./helpers/verifierTests"; const providers = [BigInt("111")]; + const issuanceDates = [1694628559, 1695047108, 1693498086]; const expirationDates = [1702404559, 1702823108, 1701274086]; const hashes = [ - ethers.getBytes("0xf760285ed09eb7bb0da39df5abd0adb608d410b357ab6415644d2b49aa64e5f1"), - ethers.getBytes("0x29b3eb7b8ee47cb0a9d83e7888f05ea5f61e3437752602282e18129d2d8b4024"), - ethers.getBytes("0x84c6f60094c95180e54fac3e9a5cfde8ca430e598e987504474151a219ae0d13"), + ethers.getBytes( + "0xf760285ed09eb7bb0da39df5abd0adb608d410b357ab6415644d2b49aa64e5f1" + ), + ethers.getBytes( + "0x29b3eb7b8ee47cb0a9d83e7888f05ea5f61e3437752602282e18129d2d8b4024" + ), + ethers.getBytes( + "0x84c6f60094c95180e54fac3e9a5cfde8ca430e598e987504474151a219ae0d13" + ), ]; -const providerMapVersion = 1; +const providerMapVersion = 0; const invalidIssuanceDates = [1694628559, 1695047108, 1693498086, 1695047109]; const invalidExpirationDates = [1702404559, 1702823108]; const invalidHashes = [ - ethers.getBytes("0xf760285ed09eb7bb0da39df5abd0adb608d410b357ab6415644d2b49aa64e5f1"), - ethers.getBytes("0x29b3eb7b8ee47cb0a9d83e7888f05ea5f61e3437752602282e18129d2d8b4024"), - ethers.getBytes("0x84c6f60094c95180e54fac3e9a5cfde8ca430e598e987504474151a219ae0d13"), - ethers.getBytes("0x84c6f60094c95180e54fac3e9a5cfde8ca430e598e987504474151a219ab1d24"), - ethers.getBytes("0x84c6f60094c95180e54fac3e9a5cfde8ca430e598e987504474151a219af2d35"), + ethers.getBytes( + "0xf760285ed09eb7bb0da39df5abd0adb608d410b357ab6415644d2b49aa64e5f1" + ), + ethers.getBytes( + "0x29b3eb7b8ee47cb0a9d83e7888f05ea5f61e3437752602282e18129d2d8b4024" + ), + ethers.getBytes( + "0x84c6f60094c95180e54fac3e9a5cfde8ca430e598e987504474151a219ae0d13" + ), + ethers.getBytes( + "0x84c6f60094c95180e54fac3e9a5cfde8ca430e598e987504474151a219ab1d24" + ), + ethers.getBytes( + "0x84c6f60094c95180e54fac3e9a5cfde8ca430e598e987504474151a219af2d35" + ), ]; -const easEncodeStamp = () => { +const easEncodePassport = () => { const schemaEncoder = new SchemaEncoder( "uint256[] providers, bytes32[] hashes, uint64[] issuanceDates, uint64[] expirationDates, uint16 providerMapVersion" ); @@ -45,7 +62,7 @@ const easEncodeStamp = () => { { name: "providerMapVersion", value: providerMapVersion, type: "uint16" }, ]); return encodedData; -} +}; const easEncodeInvalidStamp = () => { const schemaEncoder = new SchemaEncoder( @@ -56,14 +73,19 @@ const easEncodeInvalidStamp = () => { { name: "providers", value: providers, type: "uint256[]" }, { name: "hashes", value: invalidHashes, type: "bytes32[]" }, { name: "issuanceDates", value: invalidIssuanceDates, type: "uint64[]" }, - { name: "expirationDates", value: invalidExpirationDates, type: "uint64[]" }, + { + name: "expirationDates", + value: invalidExpirationDates, + type: "uint64[]", + }, { name: "providerMapVersion", value: providerMapVersion, type: "uint16" }, ]); return encodedData; -} +}; describe("GitcoinPassportDecoder", function () { - this.beforeAll(async function () { + this.beforeEach(async function () { + // this.beforeAll(async function () { const [ownerAccount, iamAcct, recipientAccount, otherAccount] = await ethers.getSigners(); @@ -79,17 +101,17 @@ describe("GitcoinPassportDecoder", function () { ); this.gitcoinAttester = await GitcoinAttester.deploy(); await this.gitcoinAttester.connect(this.owner).initialize(); - + // Deploy GitcoinVerifier const GitcoinVerifier = await ethers.getContractFactory( "GitcoinVerifier", this.owner ); this.gitcoinVerifier = await GitcoinVerifier.deploy(); - + this.gitcoinAttesterAddress = await this.gitcoinAttester.getAddress(); await this.gitcoinAttester.setEASAddress(EAS_CONTRACT_ADDRESS); - + await this.gitcoinVerifier .connect(this.owner) .initialize( @@ -134,7 +156,8 @@ describe("GitcoinPassportDecoder", function () { this.owner ); - this.stampSchemaInput = "uint256[] providers, bytes32[] hashes, uint64[] issuanceDates, uint64[] expirationDates, uint16 providerMapVersion"; + this.stampSchemaInput = + "uint256[] providers, bytes32[] hashes, uint64[] issuanceDates, uint64[] expirationDates, uint16 providerMapVersion"; this.resolverAddress = await this.gitcoinResolver.getAddress(); this.revocable = true; @@ -162,7 +185,7 @@ describe("GitcoinPassportDecoder", function () { expirationTime: 1708741995, revocable: true, refUID: ZERO_BYTES32, - data: easEncodeStamp(), + data: easEncodePassport(), value: 0, }, ], @@ -200,86 +223,94 @@ describe("GitcoinPassportDecoder", function () { const GitcoinPassportDecoder = await ethers.getContractFactory( "GitcoinPassportDecoder", - this.owner, + this.owner ); this.gitcoinPassportDecoder = await GitcoinPassportDecoder.deploy(); - await this.gitcoinPassportDecoder - .connect(this.owner) - .initialize(); + await this.gitcoinPassportDecoder.connect(this.owner).initialize(); // Initialize the sdk with the address of the EAS Schema contract address await this.gitcoinPassportDecoder.setEASAddress(EAS_CONTRACT_ADDRESS); await this.gitcoinPassportDecoder.setGitcoinResolver(this.resolverAddress); await this.gitcoinPassportDecoder.setSchemaUID(this.passportSchemaUID); - }); - - this.beforeEach(async function () { + this.passport.nonce = await this.gitcoinVerifier.recipientNonces( this.passport.multiAttestationRequest[0].data[0].recipient ); }); - describe("Creating new versions", function () { - it("should add new providers to the providers mapping and increment the version", async function () { - const providers = ["NewStamp1", "NewStamp2"]; - // Get the 0th version - const versionZero = await this.gitcoinPassportDecoder.currentVersion(); - - expect(versionZero === 0); - - await this.gitcoinPassportDecoder.connect(this.owner).createNewVersion(providers); - - // Get the current version - const currentVersion = await this.gitcoinPassportDecoder.currentVersion(); - - expect(currentVersion === 1); - - const firstProvider = await this.gitcoinPassportDecoder.providerVersions(currentVersion, 0); - - expect(firstProvider === providers[0]); - }); - - it("should not allow anyone other than owner to add new providers to the mapping", async function () { - const providers = ["NewStamp1", "NewStamp2"]; - // Get the 0th version - const versionZero = await this.gitcoinPassportDecoder.currentVersion(); - - expect(versionZero === 0); - - await expect(this.gitcoinPassportDecoder.connect(this.recipient).createNewVersion(providers)).to.be.revertedWith("Ownable: caller is not the owner"); - }); - }); - describe("Adding new providers to current version of providers", async function () { it("should append a provider to the end of an existing provider mapping", async function () { const providers = ["NewStamp1", "NewStamp2"]; - await this.gitcoinPassportDecoder.connect(this.owner).createNewVersion(providers); + await this.gitcoinPassportDecoder + .connect(this.owner) + .addProviders(providers); - await this.gitcoinPassportDecoder.connect(this.owner).addProvider("NewStamp3"); + await this.gitcoinPassportDecoder + .connect(this.owner) + .addProviders(["NewStamp3"]); const currentVersion = await this.gitcoinPassportDecoder.currentVersion(); - const lastProvider = await this.gitcoinPassportDecoder.providerVersions(currentVersion, 2); + const lastProvider = await this.gitcoinPassportDecoder.providerVersions( + currentVersion, + 2 + ); expect(lastProvider === "NewStamp3"); }); it("should not allow anyone other than owner to append new providers array in the provider mapping", async function () { - const providers = ["NewStamp1", "NewStamp2"]; + await expect( + this.gitcoinPassportDecoder + .connect(this.recipient) + .addProviders(["NewStamp3"]) + ).to.be.revertedWith("Ownable: caller is not the owner"); + }); + + it("should throw an error when trying to add the same provider twice in different function calls", async function () { + const providersForCall1 = ["NewStamp1", "NewStamp2"]; + const providersForCall2 = ["NewStamp3", "NewStamp2"]; + + await this.gitcoinPassportDecoder + .connect(this.owner) + .addProviders(providersForCall1); + + await expect( + this.gitcoinPassportDecoder + .connect(this.owner) + .addProviders(providersForCall2) + ).to.be.revertedWithCustomError( + this.gitcoinPassportDecoder, + "ProviderAlreadyExists" + ); + }); - await expect(this.gitcoinPassportDecoder.connect(this.recipient).addProvider("NewStamp3")).to.be.revertedWith("Ownable: caller is not the owner"); + it("should throw an error when trying to add the same provider twice in the same function call", async function () { + const providersForCall1 = [ + "NewStamp1", + "NewStamp2", + "NewStamp3", + "NewStamp2", + ]; + + await expect( + this.gitcoinPassportDecoder + .connect(this.owner) + .addProviders(providersForCall1) + ).to.be.revertedWithCustomError( + this.gitcoinPassportDecoder, + "ProviderAlreadyExists" + ); }); }); describe("Decoding Passports", async function () { - const mappedProviders = ["Twitter", "Google", "Ens"]; - + // providers that were created in previous tests + const providers = ["NewStamp1", "NewStamp2", "NewStamp3"]; it("should decode a user's passport", async function () { - await this.gitcoinPassportDecoder.connect(this.owner).createNewVersion(mappedProviders); - const signature = await this.iamAccount.signTypedData( this.domain, passportTypes, @@ -288,6 +319,10 @@ describe("GitcoinPassportDecoder", function () { const { v, r, s } = ethers.Signature.from(signature); + await this.gitcoinPassportDecoder + .connect(this.owner) + .addProviders(providers); + // Submit attestations const verifiedPassport = await this.gitcoinVerifier.verifyAndAttest( this.passport, @@ -305,27 +340,17 @@ describe("GitcoinPassportDecoder", function () { .connect(this.owner) .getPassport(this.recipient.address); - expect(passportTx.length === mappedProviders.length); + expect(passportTx.length === providers.length); - passportTx.forEach((cred: any) => { - mappedProviders.forEach((provider: string) => { - expect(cred[0] === provider); - }); - hashes.forEach((hash: Uint8Array) => { - expect(cred[1] === hash); - }); - issuanceDates.forEach((issuanceDate: number) => { - expect(cred[2] === issuanceDate); - }); - expirationDates.forEach((expirationDate: number) => { - expect(cred[3] === expirationDate); - }); + passportTx.forEach((cred: any, i: number) => { + expect(cred[0]).to.equal(providers[i]); + expect(ethers.getBytes(cred[1])).to.eql(hashes[i]); + expect(cred[2]).to.equal(issuanceDates[i]); + expect(cred[3]).to.equal(expirationDates[i]); }); }); it("should allow non-owners to decode a user's passport", async function () { - await this.gitcoinPassportDecoder.connect(this.owner).createNewVersion(mappedProviders); - const signature = await this.iamAccount.signTypedData( this.domain, passportTypes, @@ -334,6 +359,10 @@ describe("GitcoinPassportDecoder", function () { const { v, r, s } = ethers.Signature.from(signature); + await this.gitcoinPassportDecoder + .connect(this.owner) + .addProviders(providers); + // Submit attestations const verifiedPassport = await this.gitcoinVerifier.verifyAndAttest( this.passport, @@ -351,10 +380,10 @@ describe("GitcoinPassportDecoder", function () { .connect(this.otherAcct) .getPassport(this.recipient.address); - expect(passportTx.length === mappedProviders.length); - + expect(passportTx.length === providers.length); + passportTx.forEach((cred: any) => { - mappedProviders.forEach((provider: string) => { + providers.forEach((provider: string) => { expect(cred[0] === provider); }); hashes.forEach((hash: Uint8Array) => { @@ -374,8 +403,6 @@ describe("GitcoinPassportDecoder", function () { this.invalidPassport.multiAttestationRequest[0].data[0].recipient ); - await this.gitcoinPassportDecoder.connect(this.owner).createNewVersion(mappedProviders); - const signature = await this.iamAccount.signTypedData( this.domain, passportTypes, @@ -397,10 +424,50 @@ describe("GitcoinPassportDecoder", function () { await verifiedPassport.wait(); - expect(this.gitcoinPassportDecoder - .connect(this.owner) - .getPassport(this.recipient.address) + expect( + this.gitcoinPassportDecoder + .connect(this.owner) + .getPassport(this.recipient.address) ).to.be.revertedWithPanic(); }); }); -}); \ No newline at end of file + describe("Creating new versions", function () { + it("should add new providers to the providers mapping and increment the version", async function () { + const providers = ["NewStamp1", "NewStamp2"]; + // Get the 0th version + const versionZero = await this.gitcoinPassportDecoder.currentVersion(); + + expect(versionZero === 0); + + await this.gitcoinPassportDecoder + .connect(this.owner) + .createNewVersion(providers); + + // Get the current version + const currentVersion = await this.gitcoinPassportDecoder.currentVersion(); + + expect(currentVersion === 1); + + const firstProvider = await this.gitcoinPassportDecoder.providerVersions( + currentVersion, + 0 + ); + + expect(firstProvider === providers[0]); + }); + + it("should not allow anyone other than owner to add new providers to the mapping", async function () { + const providers = ["NewStamp1", "NewStamp2"]; + // Get the 0th version + const versionZero = await this.gitcoinPassportDecoder.currentVersion(); + + expect(versionZero === 0); + + await expect( + this.gitcoinPassportDecoder + .connect(this.recipient) + .addProviders(providers) + ).to.be.revertedWith("Ownable: caller is not the owner"); + }); + }); +});