diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9735b63..d85fcf1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,23 +8,21 @@ on: jobs: build: - strategy: - matrix: - os: [ubuntu-latest] - node-version: [16.x, 18.x] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ - runs-on: ${{ matrix.os }} + + runs-on: ubuntu-latest defaults: run: - working-directory: passportissuance + working-directory: ./passportissuance + steps: - uses: actions/checkout@v3 - - name: Use Node.js ${{ matrix.node-version }} + + - name: Use Node.js uses: actions/setup-node@v3 with: - node-version: ${{ matrix.node-version }} - cache: 'npm' - cache-dependency-path: passportissuance/package-lock.json + node-version: '20.x' - run: npm install - - run: nom run build + - run: npm run codegen + - run: npm run build - run: npm run test diff --git a/README.md b/README.md index ec42dfa..0af8484 100644 --- a/README.md +++ b/README.md @@ -35,3 +35,33 @@ Refer to the following guide from [The Graph's docs](https://thegraph.com/docs/e ## Contributing Refer to [this document from The Graph's docs](https://thegraph.com/docs/en/developing/creating-a-subgraph/) on how to set up your development environment for making changes to, and deploying new, subgraphs. + +## Testing + +Two options are available for testing: + +1. [Local Graph deployment (integration testing)](/#Local) +2. [Matchstick unit tests](/#Matchstick) + +### Local + +To test your changes to the subgraph locally you will need a local IPFS Gateway for the subgraph to deploy files to. + +Refer to [this guide](https://docs.ipfs.tech/how-to/command-line-quick-start/) to learn how to do so and ensure your gateway points to `http://localhost:5001`. + +Then run the following commands in the subgraph's directory (e.g. the [PassportIssuer](./passportissuance/) subgraph directory): + +```console +npm run codegen +npm run build +npm run create-local +npm run deploy-local +``` + +### [Matchstick](https://github.com/LimeChain/matchstick/blob/main/README.md) + +Simply run the following command to run the available tests in their respective subgraph directories (e.g. [PassportIssuer](./passportissuance/)): + +```console +npm run test +``` diff --git a/passportissuance/build/PassportIssuer/PassportIssuer.wasm b/passportissuance/build/PassportIssuer/PassportIssuer.wasm new file mode 100644 index 0000000..db291aa Binary files /dev/null and b/passportissuance/build/PassportIssuer/PassportIssuer.wasm differ diff --git a/passportissuance/build/PassportIssuer/abis/PassportIssuer.json b/passportissuance/build/PassportIssuer/abis/PassportIssuer.json new file mode 100644 index 0000000..fa5e161 --- /dev/null +++ b/passportissuance/build/PassportIssuer/abis/PassportIssuer.json @@ -0,0 +1,510 @@ +[ + { + "inputs": [], + "name": "InvalidSignature", + "type": "error" + }, + { + "inputs": [], + "name": "IssuanceIsDisabled", + "type": "error" + }, + { + "inputs": [], + "name": "IssuancesLimitReached", + "type": "error" + }, + { + "inputs": [], + "name": "NonRevocable", + "type": "error" + }, + { + "inputs": [], + "name": "NotEligible", + "type": "error" + }, + { + "inputs": [], + "name": "PassportAlreadyIssued", + "type": "error" + }, + { + "inputs": [], + "name": "PassportNotIssued", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "Attest", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "Revoke", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "claimRequiredBalance", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "revokeUnderBalance", + "type": "uint256" + } + ], + "name": "UpdateRequirements", + "type": "event" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "adminRevoke", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "claim", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "claimRequiredBalance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "enabled", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IVotingEscrow", + "name": "_veToken", + "type": "address" + }, + { + "internalType": "contract Passport", + "name": "_passToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_maxIssuances", + "type": "uint256" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "maxIssuances", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "passToken", + "outputs": [ + { + "internalType": "contract Passport", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "passportId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "passportStatus", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract ERC20", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "recoverTokens", + "outputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounce", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "revoke", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "revokeUnderBalance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "status", + "type": "bool" + } + ], + "name": "setEnabled", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_claimRequiredBalance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_revokeUnderBalance", + "type": "uint256" + } + ], + "name": "setParams", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_statement", + "type": "string" + } + ], + "name": "setStatement", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_termsURI", + "type": "string" + } + ], + "name": "setTermsURI", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "statement", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "termsURI", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalIssued", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "veToken", + "outputs": [ + { + "internalType": "contract IVotingEscrow", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "verifySignature", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/passportissuance/build/schema.graphql b/passportissuance/build/schema.graphql new file mode 100644 index 0000000..a303285 --- /dev/null +++ b/passportissuance/build/schema.graphql @@ -0,0 +1,43 @@ +type Attest @entity(immutable: true) { + id: Bytes! + _to: Bytes! # address + _tokenId: BigInt! # uint256 + blockNumber: BigInt! + blockTimestamp: BigInt! + transactionHash: Bytes! +} + +type Initialized @entity(immutable: true) { + id: Bytes! + version: Int! # uint8 + blockNumber: BigInt! + blockTimestamp: BigInt! + transactionHash: Bytes! +} + +type OwnershipTransferred @entity(immutable: true) { + id: Bytes! + previousOwner: Bytes! # address + newOwner: Bytes! # address + blockNumber: BigInt! + blockTimestamp: BigInt! + transactionHash: Bytes! +} + +type Revoke @entity(immutable: true) { + id: Bytes! + _to: Bytes! # address + _tokenId: BigInt! # uint256 + blockNumber: BigInt! + blockTimestamp: BigInt! + transactionHash: Bytes! +} + +type UpdateRequirements @entity(immutable: true) { + id: Bytes! + claimRequiredBalance: BigInt! # uint256 + revokeUnderBalance: BigInt! # uint256 + blockNumber: BigInt! + blockTimestamp: BigInt! + transactionHash: Bytes! +} diff --git a/passportissuance/build/subgraph.yaml b/passportissuance/build/subgraph.yaml new file mode 100644 index 0000000..9b5d953 --- /dev/null +++ b/passportissuance/build/subgraph.yaml @@ -0,0 +1,38 @@ +specVersion: 0.0.5 +description: Tracks revocation and issuance of Citizen Passports +repository: https://github.com/nation3/subgraphs/passportissuance +schema: + file: schema.graphql +dataSources: + - kind: ethereum + name: PassportIssuer + network: mainnet + source: + address: "0x279c0b6bfCBBA977eaF4ad1B2FFe3C208aa068aC" + abi: PassportIssuer + startBlock: 14866328 + mapping: + kind: ethereum/events + apiVersion: 0.0.7 + language: wasm/assemblyscript + entities: + - Attest + - Initialized + - OwnershipTransferred + - Revoke + - UpdateRequirements + abis: + - name: PassportIssuer + file: PassportIssuer/abis/PassportIssuer.json + eventHandlers: + - event: Attest(indexed address,indexed uint256) + handler: handleAttest + - event: Initialized(uint8) + handler: handleInitialized + - event: OwnershipTransferred(indexed address,indexed address) + handler: handleOwnershipTransferred + - event: Revoke(indexed address,indexed uint256) + handler: handleRevoke + - event: UpdateRequirements(uint256,uint256) + handler: handleUpdateRequirements + file: PassportIssuer/PassportIssuer.wasm diff --git a/passportissuance/package.json b/passportissuance/package.json index f7c9974..9e97979 100644 --- a/passportissuance/package.json +++ b/passportissuance/package.json @@ -1,6 +1,6 @@ { "name": "passportissuance", - "license": "UNLICENSED", + "license": "GPL-3.0-or-later", "scripts": { "codegen": "graph codegen", "build": "graph build", diff --git a/passportissuance/tests/.latest.json b/passportissuance/tests/.latest.json new file mode 100644 index 0000000..fe7aab2 --- /dev/null +++ b/passportissuance/tests/.latest.json @@ -0,0 +1,4 @@ +{ + "version": "0.6.0", + "timestamp": 1704358846162 +} \ No newline at end of file diff --git a/passportissuance/tests/passport-issuer-utils.ts b/passportissuance/tests/passport-issuer-utils.ts index d168f4b..f43d311 100644 --- a/passportissuance/tests/passport-issuer-utils.ts +++ b/passportissuance/tests/passport-issuer-utils.ts @@ -9,7 +9,18 @@ import { } from "../generated/PassportIssuer/PassportIssuer" export function createAttestEvent(_to: Address, _tokenId: BigInt): Attest { - let attestEvent = changetype(newMockEvent()) + let mockEvent = newMockEvent() + + let attestEvent = new Attest( + mockEvent.address, + mockEvent.logIndex, + mockEvent.transactionLogIndex, + mockEvent.logType, + mockEvent.block, + mockEvent.transaction, + [], + mockEvent.receipt + ) attestEvent.parameters = new Array() @@ -26,8 +37,19 @@ export function createAttestEvent(_to: Address, _tokenId: BigInt): Attest { return attestEvent } -export function createInitializedEvent(version: i32): Initialized { - let initializedEvent = changetype(newMockEvent()) +export function createInitializedEvent(version: number): Initialized { + let mockEvent = newMockEvent() + + let initializedEvent = new Initialized( + mockEvent.address, + mockEvent.logIndex, + mockEvent.transactionLogIndex, + mockEvent.logType, + mockEvent.block, + mockEvent.transaction, + [], + mockEvent.receipt + ) initializedEvent.parameters = new Array() @@ -45,8 +67,17 @@ export function createOwnershipTransferredEvent( previousOwner: Address, newOwner: Address ): OwnershipTransferred { - let ownershipTransferredEvent = changetype( - newMockEvent() + let mockEvent = newMockEvent() + + let ownershipTransferredEvent = new OwnershipTransferred( + mockEvent.address, + mockEvent.logIndex, + mockEvent.transactionLogIndex, + mockEvent.logType, + mockEvent.block, + mockEvent.transaction, + [], + mockEvent.receipt ) ownershipTransferredEvent.parameters = new Array() @@ -65,7 +96,18 @@ export function createOwnershipTransferredEvent( } export function createRevokeEvent(_to: Address, _tokenId: BigInt): Revoke { - let revokeEvent = changetype(newMockEvent()) + let mockEvent = newMockEvent() + + let revokeEvent = new Revoke( + mockEvent.address, + mockEvent.logIndex, + mockEvent.transactionLogIndex, + mockEvent.logType, + mockEvent.block, + mockEvent.transaction, + [], + mockEvent.receipt + ) revokeEvent.parameters = new Array() @@ -86,7 +128,18 @@ export function createUpdateRequirementsEvent( claimRequiredBalance: BigInt, revokeUnderBalance: BigInt ): UpdateRequirements { - let updateRequirementsEvent = changetype(newMockEvent()) + let mockEvent = newMockEvent() + + let updateRequirementsEvent = new UpdateRequirements( + mockEvent.address, + mockEvent.logIndex, + mockEvent.transactionLogIndex, + mockEvent.logType, + mockEvent.block, + mockEvent.transaction, + [], + mockEvent.receipt + ) updateRequirementsEvent.parameters = new Array() diff --git a/passportissuance/tests/passport-issuer.test.ts b/passportissuance/tests/passport-issuer.test.ts index b5c2f35..be5e91c 100644 --- a/passportissuance/tests/passport-issuer.test.ts +++ b/passportissuance/tests/passport-issuer.test.ts @@ -7,20 +7,15 @@ import { afterAll } from "matchstick-as/assembly/index" import { Address, BigInt } from "@graphprotocol/graph-ts" -import { Attest } from "../generated/schema" -import { Attest as AttestEvent } from "../generated/PassportIssuer/PassportIssuer" -import { handleAttest } from "../src/passport-issuer" -import { createAttestEvent } from "./passport-issuer-utils" +import { handleAttest, handleRevoke } from "../src/passport-issuer" +import { createAttestEvent, createRevokeEvent } from "./passport-issuer-utils" // Tests structure (matchstick-as >=0.5.0) // https://thegraph.com/docs/en/developer/matchstick/#tests-structure-0-5-0 -describe("Describe entity assertions", () => { +describe("Test `Attest` Event Handling", () => { + beforeAll(() => { - let _to = Address.fromString("0x0000000000000000000000000000000000000001") - let _tokenId = BigInt.fromI32(234) - let newAttestEvent = createAttestEvent(_to, _tokenId) - handleAttest(newAttestEvent) }) afterAll(() => { @@ -31,23 +26,50 @@ describe("Describe entity assertions", () => { // https://thegraph.com/docs/en/developer/matchstick/#write-a-unit-test test("Attest created and stored", () => { + let _to = Address.fromString("0x0000000000000000000000000000000000000001") + let _tokenId = BigInt.fromI32(234) + let newAttestEvent = createAttestEvent(_to, _tokenId) + handleAttest(newAttestEvent) + assert.entityCount("Attest", 1) - - // 0xa16081f360e3847006db660bae1c6d1b2e17ec2a is the default address used in newMockEvent() function + + let event_id = newAttestEvent.transaction.hash.concatI32(newAttestEvent.logIndex.toI32()) assert.fieldEquals( "Attest", - "0xa16081f360e3847006db660bae1c6d1b2e17ec2a-1", + event_id.toHexString(), "_to", "0x0000000000000000000000000000000000000001" ) assert.fieldEquals( "Attest", - "0xa16081f360e3847006db660bae1c6d1b2e17ec2a-1", + event_id.toHexString(), + "_tokenId", + "234" + ) + + }) + + test("Revoke invocation created and stored", () => { + let _to = Address.fromString("0x0000000000000000000000000000000000000001") + let _tokenId = BigInt.fromI32(234) + let newRevokeEvent = createRevokeEvent(_to, _tokenId) + handleRevoke(newRevokeEvent) + + assert.entityCount("Revoke", 1) + + let event_id = newRevokeEvent.transaction.hash.concatI32(newRevokeEvent.logIndex.toI32()) + assert.fieldEquals( + "Revoke", + event_id.toHexString(), + "_to", + "0x0000000000000000000000000000000000000001" + ) + assert.fieldEquals( + "Revoke", + event_id.toHexString(), "_tokenId", "234" ) - // More assert options: - // https://thegraph.com/docs/en/developer/matchstick/#asserts }) })