From 8493a6264f831c672880210dfd754f43504174cc Mon Sep 17 00:00:00 2001 From: schultztimothy Date: Mon, 6 Nov 2023 18:16:50 -0700 Subject: [PATCH 1/3] feat: setup decoder after deployment --- deployments/onchainInfo.json | 5 +++- hardhat.config.ts | 16 +++++++++++ scripts/setupDecoder.ts | 53 ++++++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 scripts/setupDecoder.ts diff --git a/deployments/onchainInfo.json b/deployments/onchainInfo.json index d3ae881..3e36adc 100644 --- a/deployments/onchainInfo.json +++ b/deployments/onchainInfo.json @@ -7,7 +7,7 @@ "address": "0xAcfE09Fd03f7812F022FBf636700AdEA18Fd2A7A" }, "GitcoinResolver": { - "address": "0xA10606c17d94a4DBDc4dD804d1B88fF5030aeE94" + "address": "0xAb4124548751C15d002b9c7cb9488190847b1fe1" }, "GitcoinVerifier": { "address": "0x5bC95C6e11520D25BE8c7bDf7AAc3E2eEAbD8564" @@ -22,6 +22,9 @@ "score": { "uid": "0xf72ba57b5fb4c12a5f967574bc44a11efb8be76bcfbf47ced36e1afe226ba702" } + }, + "GitcoinPassportDecoder": { + "address": "0x6BDb1C8EDa7cC998435598E43ddd346A3a9aC832" } }, "0xa": { diff --git a/hardhat.config.ts b/hardhat.config.ts index c6d23d9..13ce374 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, + }; + } } } diff --git a/scripts/setupDecoder.ts b/scripts/setupDecoder.ts new file mode 100644 index 0000000..806f4a1 --- /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.createNewVersion(providers); + + console.log(`✅ Added providers to GitcoinPassportDecoder.`); +} + +main(); From 9abea2d898213002e3c44d1b9ec02f8a256e1f4e Mon Sep 17 00:00:00 2001 From: schultztimothy Date: Tue, 7 Nov 2023 19:42:54 -0700 Subject: [PATCH 2/3] fix: add providers without incrementing version. fix break check --- contracts/GitcoinPassportDecoder.sol | 26 ++- deployments/abi/GitcoinPassportDecoder.json | 94 +++++++++ deployments/abi/GitcoinResolver.json | 7 +- deployments/abi/GitcoinVerifier.json | 3 + deployments/onchainInfo.json | 14 +- deployments/providerBitMapInfo.json | 1 + docs/03-new-deployment.md | 14 +- hardhat.config.ts | 1 + scripts/setupDecoder.ts | 2 +- test/GitcoinPassportDecoder.ts | 204 +++++++++++--------- 10 files changed, 257 insertions(+), 109 deletions(-) create mode 100644 deployments/abi/GitcoinPassportDecoder.json create mode 100644 deployments/providerBitMapInfo.json diff --git a/contracts/GitcoinPassportDecoder.sol b/contracts/GitcoinPassportDecoder.sol index 59b0a1d..52fae9e 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(); + 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(); + } + + 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; 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 3e36adc..b5ab9da 100644 --- a/deployments/onchainInfo.json +++ b/deployments/onchainInfo.json @@ -7,24 +7,24 @@ "address": "0xAcfE09Fd03f7812F022FBf636700AdEA18Fd2A7A" }, "GitcoinResolver": { - "address": "0xAb4124548751C15d002b9c7cb9488190847b1fe1" + "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": "0x6BDb1C8EDa7cC998435598E43ddd346A3a9aC832" + "address": "0x21221bFCf485c1658470017EA3bD02f238d6e8d3" } }, "0xa": { @@ -134,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 13ce374..9f4935f 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -130,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 index 806f4a1..f9fee94 100644 --- a/scripts/setupDecoder.ts +++ b/scripts/setupDecoder.ts @@ -45,7 +45,7 @@ export async function main() { ); const providers = newBitMap.map((bit) => bit.name); - await passportDecoder.createNewVersion(providers); + await passportDecoder.addProviders(providers); console.log(`✅ Added providers to GitcoinPassportDecoder.`); } diff --git a/test/GitcoinPassportDecoder.ts b/test/GitcoinPassportDecoder.ts index adcd337..10260b1 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,13 +73,17 @@ 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 () { +describe.only("GitcoinPassportDecoder", function () { this.beforeAll(async function () { const [ownerAccount, iamAcct, recipientAccount, otherAccount] = await ethers.getSigners(); @@ -79,17 +100,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 +155,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 +184,7 @@ describe("GitcoinPassportDecoder", function () { expirationTime: 1708741995, revocable: true, refUID: ZERO_BYTES32, - data: easEncodeStamp(), + data: easEncodePassport(), value: 0, }, ], @@ -200,86 +222,60 @@ 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).addProvider("NewStamp3")).to.be.revertedWith("Ownable: caller is not the owner"); + await expect( + this.gitcoinPassportDecoder + .connect(this.recipient) + .addProviders(["NewStamp3"]) + ).to.be.revertedWith("Ownable: caller is not the owner"); }); }); 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, @@ -305,27 +301,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, @@ -351,10 +337,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 +360,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 +381,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"); + }); + }); +}); From 30c315accfaa69e7d3d4c16c5d1a06409f8adbcf Mon Sep 17 00:00:00 2001 From: Gerald Iakobinyi-Pich Date: Thu, 9 Nov 2023 12:12:44 +0200 Subject: [PATCH 3/3] fix: add parameter ('provider') to the custom error ProviderAlreadyExists - added test cases to check that these errors are thrown - adjust test case for GitcoinPassportDecoder to redeploy contract before every test --- contracts/GitcoinPassportDecoder.sol | 7 ++-- test/GitcoinPassportDecoder.ts | 51 +++++++++++++++++++++++++--- 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/contracts/GitcoinPassportDecoder.sol b/contracts/GitcoinPassportDecoder.sol index 52fae9e..28f4e68 100644 --- a/contracts/GitcoinPassportDecoder.sol +++ b/contracts/GitcoinPassportDecoder.sol @@ -40,7 +40,7 @@ contract GitcoinPassportDecoder is bytes32 public schemaUID; // Errors - error ProviderAlreadyExists(); + error ProviderAlreadyExists(string provider); function initialize() public initializer { __Ownable_init(); @@ -88,10 +88,10 @@ contract GitcoinPassportDecoder is function addProviders(string[] memory providers) public onlyOwner { for (uint256 i = 0; i < providers.length; ) { if (savedProviders[providers[i]] == 1) { - revert ProviderAlreadyExists(); + revert ProviderAlreadyExists(providers[i]); } - providerVersions[currentVersion].push(providers[i]); + providerVersions[currentVersion].push(providers[i]); savedProviders[providers[i]] = 1; unchecked { @@ -226,4 +226,3 @@ contract GitcoinPassportDecoder is return passportMemoryArray; } } - diff --git a/test/GitcoinPassportDecoder.ts b/test/GitcoinPassportDecoder.ts index 10260b1..e277ffa 100644 --- a/test/GitcoinPassportDecoder.ts +++ b/test/GitcoinPassportDecoder.ts @@ -83,8 +83,9 @@ const easEncodeInvalidStamp = () => { return encodedData; }; -describe.only("GitcoinPassportDecoder", function () { - this.beforeAll(async function () { +describe("GitcoinPassportDecoder", function () { + this.beforeEach(async function () { + // this.beforeAll(async function () { const [ownerAccount, iamAcct, recipientAccount, otherAccount] = await ethers.getSigners(); @@ -233,9 +234,7 @@ describe.only("GitcoinPassportDecoder", function () { 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 ); @@ -270,6 +269,42 @@ describe.only("GitcoinPassportDecoder", function () { .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" + ); + }); + + 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 () { @@ -284,6 +319,10 @@ describe.only("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, @@ -320,6 +359,10 @@ describe.only("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,