diff --git a/schema.graphql b/schema.graphql index 8140c10..4cbbcd6 100644 --- a/schema.graphql +++ b/schema.graphql @@ -4,7 +4,7 @@ enum NftType { } type Nft @entity { - id: ID! # tokenId + tokenAddress + id: ID! # tokenAddress + tokenId tokenAddress: String! tokenId: BigInt! owner: Account! @@ -22,7 +22,7 @@ type Account @entity { } type RoleAssignment @entity { - id: ID! # rolesRegistryAddress + grantorAddress + tokenId + tokenAddress + granteeAddress + roleHash + id: ID! # rolesRegistryAddress + grantorAddress + tokenAddress + tokenId + granteeAddress + roleHash role: Role! nft: Nft! grantor: Account! @@ -35,7 +35,7 @@ type RoleAssignment @entity { } type Role @entity { - id: ID! # rolesRegistryAddress + tokenId + tokenAddress + roleHash + id: ID! # rolesRegistryAddress + tokenAddress + tokenId + roleHash rolesRegistry: RolesRegistry! roleHash: Bytes! nft: Nft! @@ -48,10 +48,11 @@ type RoleApproval @entity { grantor: Account! operator: Account! tokenAddress: String! + isApproved: Boolean! } type RolesRegistry @entity { id: ID! # contractAddress roles: [Role!] @derivedFrom(field: "rolesRegistry") roleApprovals: [RoleApproval!] @derivedFrom(field: "rolesRegistry") -} +} \ No newline at end of file diff --git a/src/erc1155/index.ts b/src/erc-1155/index.ts similarity index 100% rename from src/erc1155/index.ts rename to src/erc-1155/index.ts diff --git a/src/erc1155/transfer-batch-handler.ts b/src/erc-1155/transfer-batch-handler.ts similarity index 91% rename from src/erc1155/transfer-batch-handler.ts rename to src/erc-1155/transfer-batch-handler.ts index d07e757..d267940 100644 --- a/src/erc1155/transfer-batch-handler.ts +++ b/src/erc-1155/transfer-batch-handler.ts @@ -17,7 +17,7 @@ export function handleTransferBatch(event: TransferBatch): void { const amounts = event.params.values if (tokenIds.length != amounts.length) { - log.error('[erc1155][handleTransferBatch] tokenIds length {} does not match amounts length {}, tx {}', [ + log.error('[erc-1155][handleTransferBatch] tokenIds length {} does not match amounts length {}, tx {}', [ tokenIds.length.toString(), amounts.length.toString(), event.transaction.hash.toHex(), diff --git a/src/erc1155/transfer-single-handler.ts b/src/erc-1155/transfer-single-handler.ts similarity index 89% rename from src/erc1155/transfer-single-handler.ts rename to src/erc-1155/transfer-single-handler.ts index 73b8425..52aa2ed 100644 --- a/src/erc1155/transfer-single-handler.ts +++ b/src/erc-1155/transfer-single-handler.ts @@ -18,7 +18,7 @@ export function handleTransferSingle(event: TransferSingle): void { const nft = upsertERC1155Nft(tokenAddress, tokenId, amount, fromAddress, toAddress) - log.warning('[erc1155][handleTransferSingle] NFT {} amount {} transferred from {} to {} tx {}', [ + log.warning('[erc-1155][handleTransferSingle] NFT {} amount {} transferred from {} to {} tx {}', [ nft.id, amount.toString(), fromAddress, diff --git a/src/erc-721/index.ts b/src/erc-721/index.ts new file mode 100644 index 0000000..86b9c69 --- /dev/null +++ b/src/erc-721/index.ts @@ -0,0 +1 @@ +export { handleTransfer } from './transfer-handler' diff --git a/src/erc-721/transfer-handler.ts b/src/erc-721/transfer-handler.ts new file mode 100644 index 0000000..239e3fb --- /dev/null +++ b/src/erc-721/transfer-handler.ts @@ -0,0 +1,26 @@ +import { Transfer } from '../../generated/ERC721/ERC721' +import { upsertERC721Nft } from '../../utils' +import { log } from '@graphprotocol/graph-ts' + +/** +@dev This handler is called when a token is transferred. +@param event Transfer The event emitted by the contract. + +Example: + event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); +*/ +export function handleTransfer(event: Transfer): void { + const tokenAddress = event.address.toHex() + const tokenId = event.params.tokenId + const from = event.params.from.toHex() + const to = event.params.to.toHex() + + upsertERC721Nft(tokenAddress, tokenId, to) + + log.warning('[erc-721][handleTransfer] NFT {} transferred from {} to {} tx {}', [ + tokenId.toString(), + from, + to, + event.transaction.hash.toHex(), + ]) +} diff --git a/src/erc-7432/index.ts b/src/erc-7432/index.ts new file mode 100644 index 0000000..34daa46 --- /dev/null +++ b/src/erc-7432/index.ts @@ -0,0 +1,3 @@ +export { handleRoleGranted } from './role-granted-handler' +export { handleRoleRevoked } from './role-revoked-handler' +export { handleRoleApprovalForAll } from './role-approval-for-all-handler' diff --git a/src/erc-7432/role-approval-for-all-handler.ts b/src/erc-7432/role-approval-for-all-handler.ts new file mode 100644 index 0000000..a25a9f4 --- /dev/null +++ b/src/erc-7432/role-approval-for-all-handler.ts @@ -0,0 +1,30 @@ +import { RoleApprovalForAll } from '../../generated/ERC7432/ERC7432' +import { findOrCreateRoleApproval, findOrCreateAccount, findOrCreateRolesRegistry } from '../../utils' +import { log } from '@graphprotocol/graph-ts' + +/** +@dev This handler is called when a role approval for all is set. +@param event RoleApprovalForAll The event emitted by the contract. + +Example: + event RoleApprovalForAll(address indexed _tokenAddress, address indexed _operator, bool _isApproved); +*/ +export function handleRoleApprovalForAll(event: RoleApprovalForAll): void { + const rolesRegistryAddress = event.address.toHex() + const grantorAddress = event.transaction.from.toHex() + const operatorAddress = event.params._operator.toHex() + const tokenAddress = event.params._tokenAddress.toHex() + const isApproved = event.params._isApproved + + const grantor = findOrCreateAccount(grantorAddress) + const operator = findOrCreateAccount(operatorAddress) + const rolesRegistry = findOrCreateRolesRegistry(rolesRegistryAddress) + const roleApproval = findOrCreateRoleApproval(rolesRegistry, grantor, operator, tokenAddress) + roleApproval.isApproved = isApproved + roleApproval.save() + + log.warning('[erc-7432][handleRoleApprovalForAll] Updated RoleAssignment Approval: {} Tx: {}', [ + roleApproval.id, + event.transaction.hash.toHex(), + ]) +} diff --git a/src/erc-7432/role-granted-handler.ts b/src/erc-7432/role-granted-handler.ts new file mode 100644 index 0000000..3c16341 --- /dev/null +++ b/src/erc-7432/role-granted-handler.ts @@ -0,0 +1,61 @@ +import { log } from '@graphprotocol/graph-ts' +import { RoleGranted } from '../../generated/ERC7432/ERC7432' +import { Account, Nft } from '../../generated/schema' +import { generateERC721NftId, findOrCreateAccount, upsertRoleAssignment } from '../../utils' + +/** +@dev This handler is called when a role is granted. +@param event RoleGranted The event emitted by the contract. + +Example: + event RoleGranted( + bytes32 indexed _role, + address indexed _tokenAddress, + uint256 indexed _tokenId, + address _grantor, + address _grantee, + uint64 _expirationDate, + bool _revocable, + bytes _data + ); +*/ +export function handleRoleGranted(event: RoleGranted): void { + const tokenId = event.params._tokenId + const tokenAddress = event.params._tokenAddress.toHex() + + const nftId = generateERC721NftId(tokenAddress, tokenId) + const nft = Nft.load(nftId) + if (!nft) { + log.error('[erc-7432][handleRoleGranted] NFT {} does not exist, tx {} skipping...', [ + nftId, + event.transaction.hash.toHex(), + ]) + return + } + + const grantorAddress = event.params._grantor.toHex() + const grantorAccount = Account.load(grantorAddress) + if (!grantorAccount) { + log.error('[erc-7432][handleRoleGranted] grantor {} does not exist, tx {} skipping...', [ + grantorAddress, + event.transaction.hash.toHex(), + ]) + return + } + if (grantorAccount.id != nft.owner) { + log.error('[erc-7432][handleRoleGranted] NFT {} is not owned by {}, tx {} skipping...', [ + nftId, + grantorAccount.id, + event.transaction.hash.toHex(), + ]) + return + } + + const granteeAccount = findOrCreateAccount(event.params._grantee.toHex()) + const roleAssignment = upsertRoleAssignment(event, grantorAccount, granteeAccount, nft) + log.warning('[erc-7432][handleRoleGranted] roleAssignment: {} NFT: {} Tx: {}', [ + roleAssignment.id, + nftId, + event.transaction.hash.toHex(), + ]) +} diff --git a/src/erc-7432/role-revoked-handler.ts b/src/erc-7432/role-revoked-handler.ts new file mode 100644 index 0000000..1017e09 --- /dev/null +++ b/src/erc-7432/role-revoked-handler.ts @@ -0,0 +1,79 @@ +import { BigInt, log } from '@graphprotocol/graph-ts' +import { RoleRevoked } from '../../generated/ERC7432/ERC7432' +import { Account, Nft, RoleAssignment } from '../../generated/schema' +import { findOrCreateRole, findOrCreateRolesRegistry, generateERC721NftId, generateRoleAssignmentId } from '../../utils' + +/** +@dev This handler is called when a role is revoked. +@param event RoleRevoked The event emitted by the contract. + +Example: + event RoleRevoked( + bytes32 indexed _role, + address indexed _tokenAddress, + uint256 indexed _tokenId, + address _revoker, + address _grantee + ); +*/ +export function handleRoleRevoked(event: RoleRevoked): void { + const tokenId = event.params._tokenId + const tokenAddress = event.params._tokenAddress.toHexString() + + const nftId = generateERC721NftId(tokenAddress, tokenId) + const nft = Nft.load(nftId) + if (!nft) { + log.error('[erc-7432][handleRoleRevoked] NFT {} does not exist, tx {} skipping...', [ + nftId, + event.transaction.hash.toHex(), + ]) + return + } + + const revokerAddress = event.params._revoker.toHex() + const revoker = Account.load(revokerAddress) + if (!revoker) { + log.error('[erc-7432][handleRoleGranted] revoker {} does not exist, tx {} skipping...', [ + revokerAddress, + event.transaction.hash.toHex(), + ]) + return + } + + const granteeAddress = event.params._grantee.toHex() + const grantee = Account.load(granteeAddress) + if (!grantee) { + log.error('[erc-7432][handleRoleGranted] grantee {} does not exist, tx {} skipping...', [ + granteeAddress, + event.transaction.hash.toHex(), + ]) + return + } + + const rolesRegistry = findOrCreateRolesRegistry(event.address.toHex()) + const roleAssignmentId = generateRoleAssignmentId(rolesRegistry, revoker, grantee, nft, event.params._role) + const roleAssignment = RoleAssignment.load(roleAssignmentId) + if (!roleAssignment) { + log.error('[erc-7432][handleRoleRevoked] RoleAssignment {} does not exist, tx {} skipping...', [ + roleAssignmentId, + event.transaction.hash.toHex(), + ]) + return + } + if (event.block.timestamp > roleAssignment.expirationDate) { + log.error('[erc-7432][handleRoleRevoked] RoleAssignment {} already expired, tx {} skipping...', [ + roleAssignmentId, + event.transaction.hash.toHex(), + ]) + return + } + + roleAssignment.expirationDate = event.block.timestamp + roleAssignment.save() + + log.warning('[[erc-7432]handleRoleRevoked] Revoked RoleAssignment: {} NFT: {} tx: {}', [ + roleAssignmentId, + nftId, + event.transaction.hash.toHex(), + ]) +} diff --git a/src/erc721/index.ts b/src/erc721/index.ts deleted file mode 100644 index e8295da..0000000 --- a/src/erc721/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Transfer } from '../../generated/ERC721/ERC721' -import { upsertERC721Nft } from '../../utils' -import { log } from '@graphprotocol/graph-ts' - -export function handleTransfer(event: Transfer): void { - const tokenAddress = event.address.toHex() - const tokenId = event.params.tokenId - const accountAddress = event.params.to.toHex() - - upsertERC721Nft(tokenAddress, tokenId, accountAddress) - - log.warning('[erc721][handleTransfer] NFT {} transferred from {} to {} - tx {}', [ - tokenId.toString(), - event.params.from.toHex(), - event.params.to.toHex(), - event.transaction.hash.toHex(), - ]) -} diff --git a/src/erc7432/index.ts b/src/erc7432/index.ts deleted file mode 100644 index 2643602..0000000 --- a/src/erc7432/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { handleRoleGranted } from './role/grant-handler' -export { handleRoleRevoked } from './role/revoke-handler' -export { handleRoleApprovalForAll } from './role/role-approval-handler' diff --git a/src/erc7432/role/grant-handler.ts b/src/erc7432/role/grant-handler.ts deleted file mode 100644 index 20a7bc8..0000000 --- a/src/erc7432/role/grant-handler.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { log } from '@graphprotocol/graph-ts' -import { RoleGranted } from '../../../generated/ERC7432/ERC7432' -import { Account, Nft } from '../../../generated/schema' -import { generateERC721NftId, findOrCreateAccount, findOrCreateRoleAssignment } from '../../../utils' - -export function handleRoleGranted(event: RoleGranted): void { - const tokenId = event.params._tokenId - const tokenAddress = event.params._tokenAddress.toHex() - - const nftId = generateERC721NftId(tokenAddress, tokenId) - const nft = Nft.load(nftId) - if (!nft) { - log.warning('[handleRoleGranted] NFT {} does not exist, skipping...', [nftId]) - return - } - - const grantorAddress = event.params._grantor.toHex().toLowerCase() - const grantorAccount = Account.load(grantorAddress) - if (!grantorAccount) { - log.warning('[handleRoleGranted] grantor {} does not exist, skipping...', [grantorAddress]) - return - } - if (grantorAccount.id != nft.owner) { - log.warning('[handleRoleGranted] NFT {} is not owned by {}, skipping...', [nftId, grantorAccount.id]) - return - } - - const granteeAccount = findOrCreateAccount(event.params._grantee.toHex()) - const roleAssignment = findOrCreateRoleAssignment(event, grantorAccount, granteeAccount, nft) - log.warning('[handleRoleGranted] roleAssignment: {} NFT: {} Tx: {}', [ - roleAssignment.id, - nftId, - event.transaction.hash.toHex(), - ]) -} diff --git a/src/erc7432/role/revoke-handler.ts b/src/erc7432/role/revoke-handler.ts deleted file mode 100644 index 75e3758..0000000 --- a/src/erc7432/role/revoke-handler.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { log } from '@graphprotocol/graph-ts' -import { RoleRevoked } from '../../../generated/ERC7432/ERC7432' -import { Account, Nft, RoleAssignment } from '../../../generated/schema' -import { findOrCreateRolesRegistry, generateERC721NftId, generateRoleAssignmentId } from '../../../utils' - -export function handleRoleRevoked(event: RoleRevoked): void { - const tokenId = event.params._tokenId - const tokenAddress = event.params._tokenAddress.toHexString() - - const nftId = generateERC721NftId(tokenAddress, tokenId) - const nft = Nft.load(nftId) - if (!nft) { - log.warning('[handleRoleRevoked] NFT {} does not exist, skipping...', [nftId]) - return - } - - const revokerAddress = event.params._revoker.toHex().toLowerCase() - const revoker = Account.load(revokerAddress) - if (!revoker) { - log.warning('[handleRoleGranted] revoker {} does not exist, skipping...', [revokerAddress]) - return - } - - const granteeAddress = event.params._grantee.toHex().toLowerCase() - const grantee = Account.load(granteeAddress) - if (!grantee) { - log.warning('[handleRoleGranted] grantee {} does not exist, skipping...', [granteeAddress]) - return - } - const rolesRegistry = findOrCreateRolesRegistry(event.address.toHex()) - const roleAssignmentId = generateRoleAssignmentId(rolesRegistry, revoker, grantee, nft, event.params._role) - const roleAssignment = RoleAssignment.load(roleAssignmentId) - if (!roleAssignment) { - log.warning('[handleRoleRevoked] RoleAssignment {} does not exist, skipping...', [roleAssignmentId]) - return - } - if (event.block.timestamp > roleAssignment.expirationDate) { - log.warning('[handleRoleRevoked] RoleAssignment {} already expired, skipping...', [roleAssignmentId]) - return - } - - roleAssignment.expirationDate = event.block.timestamp - roleAssignment.save() - log.warning('[handleRoleRevoked] Revoked RoleAssignment: {} NFT: {} Tx: {}', [ - roleAssignmentId, - nftId, - event.transaction.hash.toHex(), - ]) -} diff --git a/src/erc7432/role/role-approval-handler.ts b/src/erc7432/role/role-approval-handler.ts deleted file mode 100644 index a7f196a..0000000 --- a/src/erc7432/role/role-approval-handler.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { RoleApprovalForAll } from '../../../generated/ERC7432/ERC7432' -import { - findOrCreateAccount, - findOrCreateRoleApproval, - deleteRoleApproval, - generateRoleApprovalId, - findOrCreateRolesRegistry, -} from '../../../utils' -import { log } from '@graphprotocol/graph-ts' - -export function handleRoleApprovalForAll(event: RoleApprovalForAll): void { - const rolesRegistryAddress = event.address.toHex() - const grantorAddress = event.transaction.from.toHex() - const operatorAddress = event.params._operator.toHex() - const tokenAddress = event.params._tokenAddress.toHex() - const isApproved = event.params._isApproved - - const grantorAccount = findOrCreateAccount(grantorAddress) - const operatorAccount = findOrCreateAccount(operatorAddress) - const rolesRegistry = findOrCreateRolesRegistry(rolesRegistryAddress) - - if (isApproved) { - const roleApproval = findOrCreateRoleApproval(rolesRegistry, grantorAccount, operatorAccount, tokenAddress) - log.warning('[handleRoleApprovalForAll] Updated RoleAssignment Approval: {} Tx: {}', [ - roleApproval.id, - event.transaction.hash.toHex(), - ]) - } else { - deleteRoleApproval(rolesRegistry, grantorAccount, operatorAccount, tokenAddress) - log.warning('[handleRoleApprovalForAll] Removed RoleAssignment Approval: {} Tx: {}', [ - generateRoleApprovalId(rolesRegistry, grantorAccount, operatorAccount, tokenAddress), - event.transaction.hash.toHex(), - ]) - } -} diff --git a/subgraph.template.yaml b/subgraph.template.yaml index 711c5ed..9ad5d19 100644 --- a/subgraph.template.yaml +++ b/subgraph.template.yaml @@ -24,7 +24,7 @@ dataSources: eventHandlers: - event: Transfer(indexed address,indexed address,indexed uint256) handler: handleTransfer - file: ./src/erc721/index.ts + file: ./src/erc-721/index.ts {{/ERC721}} {{#ERC1155}} @@ -50,7 +50,7 @@ dataSources: handler: handleTransferSingle - event: TransferBatch(indexed address,indexed address,indexed address,uint256[],uint256[]) handler: handleTransferBatch - file: ./src/erc1155/index.ts + file: ./src/erc-1155/index.ts {{/ERC1155}} - name: ERC7432 @@ -82,4 +82,4 @@ dataSources: handler: handleRoleRevoked - event: RoleApprovalForAll(indexed address,indexed address,bool) handler: handleRoleApprovalForAll - file: ./src/erc7432/index.ts + file: ./src/erc-7432/index.ts \ No newline at end of file diff --git a/tests/erc1155/transfer-batch-handler.test.ts b/tests/erc-1155/transfer-batch-handler.test.ts similarity index 97% rename from tests/erc1155/transfer-batch-handler.test.ts rename to tests/erc-1155/transfer-batch-handler.test.ts index feba0f0..b30b348 100644 --- a/tests/erc1155/transfer-batch-handler.test.ts +++ b/tests/erc-1155/transfer-batch-handler.test.ts @@ -1,8 +1,8 @@ import { assert, describe, test, clearStore, afterEach } from 'matchstick-as' import { NftType, generateERC1155NftId } from '../../utils' -import { createTransferBatchEvent } from '../helpers/events' +import { createTransferBatchEvent } from '../mocks/events' import { Addresses, Amounts, TokenIds, ZERO_ADDRESS } from '../helpers/contants' -import { handleTransferBatch } from '../../src/erc1155' +import { handleTransferBatch } from '../../src/erc-1155' describe('ERC-1155 Transfer Batch Handler', () => { afterEach(() => { diff --git a/tests/erc1155/transfer-single-handler.test.ts b/tests/erc-1155/transfer-single-handler.test.ts similarity index 97% rename from tests/erc1155/transfer-single-handler.test.ts rename to tests/erc-1155/transfer-single-handler.test.ts index 1a6b7f2..cdb7381 100644 --- a/tests/erc1155/transfer-single-handler.test.ts +++ b/tests/erc-1155/transfer-single-handler.test.ts @@ -1,8 +1,8 @@ import { assert, describe, test, clearStore, afterEach } from 'matchstick-as' import { NftType, generateERC1155NftId } from '../../utils' -import { createTransferSingleEvent } from '../helpers/events' +import { createTransferSingleEvent } from '../mocks/events' import { Addresses, Amounts, TokenIds, ZERO_ADDRESS } from '../helpers/contants' -import { handleTransferSingle } from '../../src/erc1155' +import { handleTransferSingle } from '../../src/erc-1155' describe('ERC-1155 Transfer Single Handler', () => { afterEach(() => { diff --git a/tests/erc721/transfer-handler.test.ts b/tests/erc-721/transfer-handler.test.ts similarity index 79% rename from tests/erc721/transfer-handler.test.ts rename to tests/erc-721/transfer-handler.test.ts index fda360c..7cb8ee6 100644 --- a/tests/erc721/transfer-handler.test.ts +++ b/tests/erc-721/transfer-handler.test.ts @@ -1,10 +1,11 @@ import { assert, describe, test, clearStore, afterAll } from 'matchstick-as' -import { handleTransfer } from '../../src/erc721' +import { BigInt } from '@graphprotocol/graph-ts' +import { handleTransfer } from '../../src/erc-721' import { generateERC721NftId } from '../../utils' -import { createTransferEvent } from '../helpers/events' +import { createTransferEvent } from '../mocks/events' import { Addresses, ZERO_ADDRESS } from '../helpers/contants' -const tokenId = '123' +const tokenId = BigInt.fromI32(1) describe('ERC-721 Transfer Handler', () => { afterAll(() => { @@ -15,7 +16,7 @@ describe('ERC-721 Transfer Handler', () => { assert.entityCount('Nft', 0) assert.entityCount('Account', 0) - const event = createTransferEvent(Addresses[0], Addresses[1], tokenId, ZERO_ADDRESS) + const event = createTransferEvent(Addresses[0], Addresses[1], tokenId.toString(), ZERO_ADDRESS) handleTransfer(event) assert.entityCount('Nft', 1) @@ -23,7 +24,7 @@ describe('ERC-721 Transfer Handler', () => { const _id = generateERC721NftId(event.address.toHexString(), event.params.tokenId) assert.fieldEquals('Nft', _id, 'tokenAddress', ZERO_ADDRESS) - assert.fieldEquals('Nft', _id, 'tokenId', tokenId) + assert.fieldEquals('Nft', _id, 'tokenId', tokenId.toString()) assert.fieldEquals('Nft', _id, 'owner', Addresses[1]) }) @@ -31,7 +32,7 @@ describe('ERC-721 Transfer Handler', () => { assert.entityCount('Nft', 1) assert.entityCount('Account', 1) - const event = createTransferEvent(Addresses[1], Addresses[2], tokenId, ZERO_ADDRESS) + const event = createTransferEvent(Addresses[1], Addresses[2], tokenId.toString(), ZERO_ADDRESS) handleTransfer(event) assert.entityCount('Nft', 1) @@ -39,7 +40,7 @@ describe('ERC-721 Transfer Handler', () => { const _id = generateERC721NftId(event.address.toHexString(), event.params.tokenId) assert.fieldEquals('Nft', _id, 'tokenAddress', ZERO_ADDRESS) - assert.fieldEquals('Nft', _id, 'tokenId', tokenId) + assert.fieldEquals('Nft', _id, 'tokenId', tokenId.toString()) assert.fieldEquals('Nft', _id, 'owner', Addresses[2]) }) @@ -47,7 +48,7 @@ describe('ERC-721 Transfer Handler', () => { assert.entityCount('Nft', 1) assert.entityCount('Account', 2) - const event = createTransferEvent(Addresses[0], Addresses[2], tokenId, ZERO_ADDRESS) + const event = createTransferEvent(Addresses[0], Addresses[2], tokenId.toString(), ZERO_ADDRESS) handleTransfer(event) assert.entityCount('Nft', 1) @@ -55,7 +56,7 @@ describe('ERC-721 Transfer Handler', () => { const _id = generateERC721NftId(event.address.toHexString(), event.params.tokenId) assert.fieldEquals('Nft', _id, 'tokenAddress', ZERO_ADDRESS) - assert.fieldEquals('Nft', _id, 'tokenId', tokenId) + assert.fieldEquals('Nft', _id, 'tokenId', tokenId.toString()) assert.fieldEquals('Nft', _id, 'owner', Addresses[2]) }) }) diff --git a/tests/erc7432/approval-handler.test.ts b/tests/erc-7432/approval-handler.test.ts similarity index 67% rename from tests/erc7432/approval-handler.test.ts rename to tests/erc-7432/approval-handler.test.ts index 62c388d..6a4a9fd 100644 --- a/tests/erc7432/approval-handler.test.ts +++ b/tests/erc-7432/approval-handler.test.ts @@ -1,8 +1,9 @@ import { assert, describe, test, clearStore, afterEach } from 'matchstick-as' -import { createNewRoleApprovalForAllEvent } from '../helpers/events' -import { validateRoleApproval, createMockRoleApproval } from '../helpers/entities' +import { createNewRoleApprovalForAllEvent } from '../mocks/events' +import { createMockRoleApproval } from '../mocks/entities' import { Addresses, ZERO_ADDRESS } from '../helpers/contants' -import { handleRoleApprovalForAll } from '../../src/erc7432' +import { handleRoleApprovalForAll } from '../../src/erc-7432' +import { validateRoleApproval } from '../helpers/assertion' const grantor = Addresses[0] const operator = Addresses[1] @@ -15,36 +16,38 @@ describe('ERC-7432 RoleApprovalForAll Handler', () => { }) describe('When RoleApproval exists', () => { - test('should remove approval when is set to false', () => { - createMockRoleApproval(grantor, operator, tokenAddress, rolesRegistryAddress) + test('should NOT remove approval when is set to false', () => { + createMockRoleApproval(grantor, operator, tokenAddress, rolesRegistryAddress, true) assert.entityCount('RoleApproval', 1) const event = createNewRoleApprovalForAllEvent(grantor, operator, tokenAddress, false) handleRoleApprovalForAll(event) - assert.entityCount('RoleApproval', 0) + assert.entityCount('RoleApproval', 1) + validateRoleApproval(rolesRegistryAddress, grantor, operator, tokenAddress, false) }) - test('should not do anything when is set to true', () => { - createMockRoleApproval(grantor, operator, tokenAddress, rolesRegistryAddress) + test('should update when is set to true', () => { + createMockRoleApproval(grantor, operator, tokenAddress, rolesRegistryAddress, true) assert.entityCount('RoleApproval', 1) const event = createNewRoleApprovalForAllEvent(grantor, operator, tokenAddress, true) handleRoleApprovalForAll(event) assert.entityCount('RoleApproval', 1) - validateRoleApproval(rolesRegistryAddress, grantor, operator, tokenAddress) + validateRoleApproval(rolesRegistryAddress, grantor, operator, tokenAddress, true) }) }) describe('When RoleApproval does not exist', () => { - test('should not do anything when approval is set to false', () => { + test('should create approval when is set to false', () => { assert.entityCount('RoleApproval', 0) const event = createNewRoleApprovalForAllEvent(grantor, operator, tokenAddress, false) handleRoleApprovalForAll(event) - assert.entityCount('RoleApproval', 0) + assert.entityCount('RoleApproval', 1) + validateRoleApproval(rolesRegistryAddress, grantor, operator, tokenAddress, false) }) test('should create approval when approval is set to true', () => { @@ -54,7 +57,7 @@ describe('ERC-7432 RoleApprovalForAll Handler', () => { handleRoleApprovalForAll(event) assert.entityCount('RoleApproval', 1) - validateRoleApproval(rolesRegistryAddress, grantor, operator, tokenAddress) + validateRoleApproval(rolesRegistryAddress, grantor, operator, tokenAddress, true) }) }) }) diff --git a/tests/erc7432/grant-handler.test.ts b/tests/erc-7432/grant-handler.test.ts similarity index 96% rename from tests/erc7432/grant-handler.test.ts rename to tests/erc-7432/grant-handler.test.ts index 616c0a2..41c071c 100644 --- a/tests/erc7432/grant-handler.test.ts +++ b/tests/erc-7432/grant-handler.test.ts @@ -1,10 +1,11 @@ import { assert, describe, test, clearStore, afterEach } from 'matchstick-as' -import { createNewRoleGrantedEvent } from '../helpers/events' -import { handleRoleGranted } from '../../src/erc7432' +import { createNewRoleGrantedEvent } from '../mocks/events' +import { handleRoleGranted } from '../../src/erc-7432' import { Addresses, ZERO_ADDRESS } from '../helpers/contants' import { BigInt, Bytes } from '@graphprotocol/graph-ts' -import { createMockAccount, createMockNft, validateRole } from '../helpers/entities' +import { createMockAccount, createMockNft } from '../mocks/entities' import { Account } from '../../generated/schema' +import { validateRole } from '../helpers/assertion' const RoleAssignmentId = Bytes.fromUTF8('0xGrantRole') const tokenAddress = Addresses[0] diff --git a/tests/erc7432/revoke-handler.test.ts b/tests/erc-7432/revoke-handler.test.ts similarity index 96% rename from tests/erc7432/revoke-handler.test.ts rename to tests/erc-7432/revoke-handler.test.ts index 5edaca0..376fcce 100644 --- a/tests/erc7432/revoke-handler.test.ts +++ b/tests/erc-7432/revoke-handler.test.ts @@ -1,11 +1,12 @@ import { assert, describe, test, clearStore, afterEach } from 'matchstick-as' -import { createNewRoleRevokedEvent } from '../helpers/events' -import { handleRoleRevoked } from '../../src/erc7432' +import { createNewRoleRevokedEvent } from '../mocks/events' +import { handleRoleRevoked } from '../../src/erc-7432' import { Bytes, BigInt } from '@graphprotocol/graph-ts' -import { createMockAccount, createMockNft, createMockRoleAssignment, validateRole } from '../helpers/entities' +import { createMockAccount, createMockNft, createMockRoleAssignment } from '../mocks/entities' import { Addresses, ONE, TWO, ZERO_ADDRESS } from '../helpers/contants' import { findOrCreateRolesRegistry, generateERC721NftId, generateRoleAssignmentId } from '../../utils' import { Account, Nft } from '../../generated/schema' +import { validateRole } from '../helpers/assertion' const tokenId = '123' const RoleAssignmentId = Bytes.fromUTF8('0xGrantRole') diff --git a/tests/helpers/assertion.ts b/tests/helpers/assertion.ts new file mode 100644 index 0000000..a753789 --- /dev/null +++ b/tests/helpers/assertion.ts @@ -0,0 +1,57 @@ +import { assert } from 'matchstick-as' +import { Account, Nft } from '../../generated/schema' +import { + findOrCreateRolesRegistry, + generateRoleApprovalId, + generateRoleAssignmentId, + generateRoleId, +} from '../../utils' +import { BigInt, Bytes } from '@graphprotocol/graph-ts' + +export function validateRole( + grantor: Account, + grantee: Account, + nft: Nft, + roleAssignment: Bytes, + expirationDate: BigInt, + data: Bytes, + rolesRegistryAddress: string, +): void { + const rolesRegistry = findOrCreateRolesRegistry(rolesRegistryAddress) + const roleId = generateRoleId(rolesRegistry, nft, roleAssignment) + assert.fieldEquals('Role', roleId, 'roleHash', roleAssignment.toHex()) + assert.fieldEquals('Role', roleId, 'nft', nft.id) + + const roleAssignmentId = generateRoleAssignmentId(rolesRegistry, grantor, grantee, nft, roleAssignment) + assert.fieldEquals( + 'RoleAssignment', + roleAssignmentId, + 'role', + `${rolesRegistryAddress}-${nft.id}-${roleAssignment.toHex()}`, + ) + assert.fieldEquals('RoleAssignment', roleAssignmentId, 'nft', nft.id) + assert.fieldEquals('RoleAssignment', roleAssignmentId, 'grantor', grantor.id) + assert.fieldEquals('RoleAssignment', roleAssignmentId, 'grantee', grantee.id) + assert.fieldEquals('RoleAssignment', roleAssignmentId, 'expirationDate', expirationDate.toString()) + assert.fieldEquals('RoleAssignment', roleAssignmentId, 'data', data.toHex()) +} + +export function validateRoleApproval( + rolesRegistryAddress: string, + grantor: string, + operator: string, + tokenAddress: string, + isApproved: boolean, +): void { + const rolesRegistry = findOrCreateRolesRegistry(rolesRegistryAddress) + const roleApprovalId = generateRoleApprovalId( + rolesRegistry, + new Account(grantor.toLowerCase()), + new Account(operator.toLowerCase()), + tokenAddress, + ) + assert.fieldEquals('RoleApproval', roleApprovalId, 'grantor', grantor) + assert.fieldEquals('RoleApproval', roleApprovalId, 'operator', operator) + assert.fieldEquals('RoleApproval', roleApprovalId, 'tokenAddress', tokenAddress) + assert.fieldEquals('RoleApproval', roleApprovalId, 'isApproved', isApproved.toString()) +} diff --git a/tests/helpers/events.ts b/tests/helpers/events.ts index 521909f..05cddbf 100644 --- a/tests/helpers/events.ts +++ b/tests/helpers/events.ts @@ -1,137 +1,55 @@ -import { newMockEvent } from 'matchstick-as' -import { Transfer } from '../../generated/ERC721/ERC721' import { Address, BigInt, Bytes, ethereum } from '@graphprotocol/graph-ts' -import { RoleGranted, RoleRevoked, RoleApprovalForAll } from '../../generated/ERC7432/ERC7432' -import { Nft } from '../../generated/schema' -import { ZERO_ADDRESS } from './contants' -import { TransferBatch, TransferSingle } from '../../generated/ERC1155/ERC1155' -export function createTransferEvent(from: string, to: string, tokenId: string, address: string): Transfer { - const event = changetype(newMockEvent()) - event.parameters = new Array() - event.parameters.push(buildEventParamAddress('from', from)) - event.parameters.push(buildEventParamAddress('to', to)) - event.parameters.push(buildEventParamUint('tokenId', BigInt.fromString(tokenId))) - event.address = Address.fromString(address) - return event -} - -export function createTransferSingleEvent( - operator: string, - from: string, - to: string, - tokenId: BigInt, - amount: BigInt, - address: string, -): TransferSingle { - const event = changetype(newMockEvent()) - event.parameters = new Array() - event.parameters.push(buildEventParamAddress('operator', operator)) - event.parameters.push(buildEventParamAddress('from', from)) - event.parameters.push(buildEventParamAddress('to', to)) - event.parameters.push(buildEventParamUint('id', tokenId)) - event.parameters.push(buildEventParamUint('value', amount)) - event.address = Address.fromString(address) - return event -} - -export function createTransferBatchEvent( - operator: string, - from: string, - to: string, - tokenIds: Array, - amounts: Array, - address: string, -): TransferBatch { - const event = changetype(newMockEvent()) - event.parameters = new Array() - event.parameters.push(buildEventParamAddress('operator', operator)) - event.parameters.push(buildEventParamAddress('from', from)) - event.parameters.push(buildEventParamAddress('to', to)) - event.parameters.push(buildEventParamUintArray('ids', tokenIds)) - event.parameters.push(buildEventParamUintArray('values', amounts)) - event.address = Address.fromString(address) - return event -} - -export function createNewRoleRevokedEvent( - roleAssignment: Bytes, - nft: Nft, - revoker: string, - grantee: string, -): RoleRevoked { - const event = changetype(newMockEvent()) - event.address = Address.fromString(ZERO_ADDRESS) - event.parameters = new Array() - event.parameters.push(buildEventParamBytes('_role', roleAssignment)) - event.parameters.push(buildEventParamAddress('_tokenAddress', nft.tokenAddress)) - event.parameters.push(buildEventParamUint('_tokenId', nft.tokenId)) - event.parameters.push(buildEventParamAddress('_revoker', revoker)) - event.parameters.push(buildEventParamAddress('_grantee', grantee)) - return event -} - -export function createNewRoleGrantedEvent( - roleAssignment: Bytes, - tokenId: string, - tokenAddress: string, - grantee: string, - grantor: string, - expirationDate: BigInt, - revocable: boolean, - data: Bytes, -): RoleGranted { - const event = changetype(newMockEvent()) - event.address = Address.fromString(ZERO_ADDRESS) - event.parameters = new Array() - event.parameters.push(buildEventParamBytes('_role', roleAssignment)) - event.parameters.push(buildEventParamAddress('_tokenAddress', tokenAddress)) - event.parameters.push(buildEventParamUint('_tokenId', BigInt.fromString(tokenId))) - event.parameters.push(buildEventParamAddress('_grantor', grantor)) - event.parameters.push(buildEventParamAddress('_grantee', grantee)) - event.parameters.push(buildEventParamUint('_expirationDate', expirationDate)) - event.parameters.push(buildEventParamBoolean('_revocable', revocable)) - event.parameters.push(buildEventParamBytes('_data', data)) - return event -} - -export function createNewRoleApprovalForAllEvent( - grantor: string, - operator: string, - tokenAddress: string, - isApproved: boolean, -): RoleApprovalForAll { - const event = changetype(newMockEvent()) - event.address = Address.fromString(ZERO_ADDRESS) - event.parameters = new Array() - event.transaction.from = Address.fromString(grantor) - event.parameters.push(buildEventParamAddress('_tokenAddress', tokenAddress)) - event.parameters.push(buildEventParamAddress('_operator', operator)) - event.parameters.push(buildEventParamBoolean('_isApproved', isApproved)) - return event -} - -function buildEventParamBoolean(name: string, value: boolean): ethereum.EventParam { +/** + * @dev Build an event parameter of type boolean + * @param name The name of the parameter. + * @param value A boolean value. + * @returns The event parameter. + */ +export function buildEventParamBoolean(name: string, value: boolean): ethereum.EventParam { const ethAddress = ethereum.Value.fromBoolean(value) return new ethereum.EventParam(name, ethAddress) } - -function buildEventParamAddress(name: string, address: string): ethereum.EventParam { +/** + * @dev Build an event parameter of type address + * @param name The name of the parameter. + * @param address A string value to be casted to Address. + * @returns The event parameter. + */ +export function buildEventParamAddress(name: string, address: string): ethereum.EventParam { const ethAddress = ethereum.Value.fromAddress(Address.fromString(address)) return new ethereum.EventParam(name, ethAddress) } -function buildEventParamUint(name: string, value: BigInt): ethereum.EventParam { +/** + * @dev Build an event parameter of type positive BigInt + * @param name The name of the parameter. + * @param value A BigInt value to be casted to UnsignedBigInt. + * @returns The event parameter. + */ +export function buildEventParamUint(name: string, value: BigInt): ethereum.EventParam { const ethValue = ethereum.Value.fromUnsignedBigInt(value) return new ethereum.EventParam(name, ethValue) } -function buildEventParamUintArray(name: string, value: Array): ethereum.EventParam { - const ethValue = ethereum.Value.fromUnsignedBigIntArray(value) +/** + * @dev Build an event parameter of type string + * @param name The name of the parameter. + * @param value A string value to be casted to Bytes. + * @returns The event parameter. + */ +export function buildEventParamBytes(name: string, value: Bytes): ethereum.EventParam { + const ethValue = ethereum.Value.fromFixedBytes(value) return new ethereum.EventParam(name, ethValue) } -function buildEventParamBytes(name: string, value: Bytes): ethereum.EventParam { - const ethValue = ethereum.Value.fromFixedBytes(value) +/** + * @dev Build an event parameter of type array of UnsignedBigInt + * @param name The name of the parameter. + * @param value An array of strings to be casted to Array of UnsignedBigInt. + * @returns The event parameter. + */ +export function buildEventParamUintArray(name: string, value: Array): ethereum.EventParam { + const ethValue = ethereum.Value.fromUnsignedBigIntArray(value) return new ethereum.EventParam(name, ethValue) } diff --git a/tests/helpers/entities.ts b/tests/mocks/entities.ts similarity index 59% rename from tests/helpers/entities.ts rename to tests/mocks/entities.ts index afeb501..d91141f 100644 --- a/tests/helpers/entities.ts +++ b/tests/mocks/entities.ts @@ -8,7 +8,6 @@ import { findOrCreateRolesRegistry, NftType, } from '../../utils' -import { assert } from 'matchstick-as' export function createMockNft(tokenAddress: string, tokenId: string, ownerAddress: string): Nft { const nft = new Nft(generateERC721NftId(tokenAddress, BigInt.fromString(tokenId))) @@ -71,6 +70,7 @@ export function createMockRoleApproval( operator: string, tokenAddress: string, rolesRegistryAddress: string, + isApproved: boolean, ): RoleApproval { const rolesRegistry = findOrCreateRolesRegistry(rolesRegistryAddress) const roleApprovalId = generateRoleApprovalId( @@ -84,52 +84,7 @@ export function createMockRoleApproval( roleApproval.operator = operator roleApproval.tokenAddress = tokenAddress roleApproval.rolesRegistry = rolesRegistryAddress + roleApproval.isApproved = isApproved roleApproval.save() return roleApproval } - -export function validateRole( - grantor: Account, - grantee: Account, - nft: Nft, - roleAssignment: Bytes, - expirationDate: BigInt, - data: Bytes, - rolesRegistryAddress: string, -): void { - const rolesRegistry = findOrCreateRolesRegistry(rolesRegistryAddress) - const roleId = generateRoleId(rolesRegistry, nft, roleAssignment) - assert.fieldEquals('Role', roleId, 'roleHash', roleAssignment.toHex()) - assert.fieldEquals('Role', roleId, 'nft', nft.id) - - const roleAssignmentId = generateRoleAssignmentId(rolesRegistry, grantor, grantee, nft, roleAssignment) - assert.fieldEquals( - 'RoleAssignment', - roleAssignmentId, - 'role', - `${rolesRegistryAddress}-${nft.id}-${roleAssignment.toHex()}`, - ) - assert.fieldEquals('RoleAssignment', roleAssignmentId, 'nft', nft.id) - assert.fieldEquals('RoleAssignment', roleAssignmentId, 'grantor', grantor.id) - assert.fieldEquals('RoleAssignment', roleAssignmentId, 'grantee', grantee.id) - assert.fieldEquals('RoleAssignment', roleAssignmentId, 'expirationDate', expirationDate.toString()) - assert.fieldEquals('RoleAssignment', roleAssignmentId, 'data', data.toHex()) -} - -export function validateRoleApproval( - rolesRegistryAddress: string, - grantor: string, - operator: string, - tokenAddress: string, -): void { - const rolesRegistry = findOrCreateRolesRegistry(rolesRegistryAddress) - const roleApprovalId = generateRoleApprovalId( - rolesRegistry, - new Account(grantor.toLowerCase()), - new Account(operator.toLowerCase()), - tokenAddress, - ) - assert.fieldEquals('RoleApproval', roleApprovalId, 'grantor', grantor) - assert.fieldEquals('RoleApproval', roleApprovalId, 'operator', operator) - assert.fieldEquals('RoleApproval', roleApprovalId, 'tokenAddress', tokenAddress) -} diff --git a/tests/mocks/events.ts b/tests/mocks/events.ts new file mode 100644 index 0000000..e6449fb --- /dev/null +++ b/tests/mocks/events.ts @@ -0,0 +1,180 @@ +import { newMockEvent } from 'matchstick-as' +import { Address, BigInt, Bytes, ethereum } from '@graphprotocol/graph-ts' +import { + buildEventParamAddress, + buildEventParamBoolean, + buildEventParamBytes, + buildEventParamUint, + buildEventParamUintArray, +} from '../helpers/events' +import { TransferBatch, TransferSingle } from '../../generated/ERC1155/ERC1155' +import { Transfer } from '../../generated/ERC721/ERC721' +import { RoleApprovalForAll, RoleGranted, RoleRevoked } from '../../generated/ERC7432/ERC7432' +import { ZERO_ADDRESS } from '../helpers/contants' +import { Nft } from '../../generated/schema' + +/** +@dev Creates a mock for the event Transfer + +Example: + event Transfer(address indexed from, address indexed to, uint256 indexed tokenId) + */ +export function createTransferEvent(from: string, to: string, tokenId: string, address: string): Transfer { + const event = changetype(newMockEvent()) + event.parameters = new Array() + event.parameters.push(buildEventParamAddress('from', from)) + event.parameters.push(buildEventParamAddress('to', to)) + event.parameters.push(buildEventParamUint('tokenId', BigInt.fromString(tokenId))) + event.address = Address.fromString(address) + return event +} + +/** +@dev Creates a mock for the event TransferSingle + +Example: + event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value) + */ +export function createTransferSingleEvent( + operator: string, + from: string, + to: string, + tokenId: BigInt, + amount: BigInt, + address: string, +): TransferSingle { + const event = changetype(newMockEvent()) + event.parameters = new Array() + event.parameters.push(buildEventParamAddress('operator', operator)) + event.parameters.push(buildEventParamAddress('from', from)) + event.parameters.push(buildEventParamAddress('to', to)) + event.parameters.push(buildEventParamUint('id', tokenId)) + event.parameters.push(buildEventParamUint('value', amount)) + event.address = Address.fromString(address) + return event +} + +/** +@dev Creates a mock for the event TransferBatch + +Example: + event TransferBatch( + address indexed operator, + address indexed from, + address indexed to, + uint256[] ids, + uint256[] values + ) + */ +export function createTransferBatchEvent( + operator: string, + from: string, + to: string, + tokenIds: Array, + amounts: Array, + address: string, +): TransferBatch { + const event = changetype(newMockEvent()) + event.parameters = new Array() + event.parameters.push(buildEventParamAddress('operator', operator)) + event.parameters.push(buildEventParamAddress('from', from)) + event.parameters.push(buildEventParamAddress('to', to)) + event.parameters.push(buildEventParamUintArray('ids', tokenIds)) + event.parameters.push(buildEventParamUintArray('values', amounts)) + event.address = Address.fromString(address) + return event +} + +/** +@dev Creates a mock for the event RoleRevoked + +Example: + event RoleRevoked( + bytes32 indexed _role, + address indexed _tokenAddress, + uint256 indexed _tokenId, + address _revoker, + address _grantee + ) + */ +export function createNewRoleRevokedEvent( + roleAssignment: Bytes, + nft: Nft, + revoker: string, + grantee: string, +): RoleRevoked { + const event = changetype(newMockEvent()) + event.address = Address.fromString(ZERO_ADDRESS) + event.parameters = new Array() + event.parameters.push(buildEventParamBytes('_role', roleAssignment)) + event.parameters.push(buildEventParamAddress('_tokenAddress', nft.tokenAddress)) + event.parameters.push(buildEventParamUint('_tokenId', nft.tokenId)) + event.parameters.push(buildEventParamAddress('_revoker', revoker)) + event.parameters.push(buildEventParamAddress('_grantee', grantee)) + return event +} + +/** +@dev Creates a mock for the event RoleGranted + +Example: + event RoleGranted( + bytes32 indexed _role, + address indexed _tokenAddress, + uint256 indexed _tokenId, + address _grantor, + address _grantee, + uint64 _expirationDate, + bool _revocable, + bytes _data + ) + */ +export function createNewRoleGrantedEvent( + roleAssignment: Bytes, + tokenId: string, + tokenAddress: string, + grantee: string, + grantor: string, + expirationDate: BigInt, + revocable: boolean, + data: Bytes, +): RoleGranted { + const event = changetype(newMockEvent()) + event.address = Address.fromString(ZERO_ADDRESS) + event.parameters = new Array() + event.parameters.push(buildEventParamBytes('_role', roleAssignment)) + event.parameters.push(buildEventParamAddress('_tokenAddress', tokenAddress)) + event.parameters.push(buildEventParamUint('_tokenId', BigInt.fromString(tokenId))) + event.parameters.push(buildEventParamAddress('_grantor', grantor)) + event.parameters.push(buildEventParamAddress('_grantee', grantee)) + event.parameters.push(buildEventParamUint('_expirationDate', expirationDate)) + event.parameters.push(buildEventParamBoolean('_revocable', revocable)) + event.parameters.push(buildEventParamBytes('_data', data)) + return event +} + +/** +@dev Creates a mock for the event RoleApprovalForAll + +Example: + event RoleApprovalForAll( + address indexed _tokenAddress, + address indexed _operator, + bool _isApproved + ); + */ +export function createNewRoleApprovalForAllEvent( + grantor: string, + operator: string, + tokenAddress: string, + isApproved: boolean, +): RoleApprovalForAll { + const event = changetype(newMockEvent()) + event.address = Address.fromString(ZERO_ADDRESS) + event.parameters = new Array() + event.transaction.from = Address.fromString(grantor) + event.parameters.push(buildEventParamAddress('_tokenAddress', tokenAddress)) + event.parameters.push(buildEventParamAddress('_operator', operator)) + event.parameters.push(buildEventParamBoolean('_isApproved', isApproved)) + return event +} diff --git a/tests/mocks/values.ts b/tests/mocks/values.ts new file mode 100644 index 0000000..c20dd8c --- /dev/null +++ b/tests/mocks/values.ts @@ -0,0 +1,25 @@ +import { Bytes } from '@graphprotocol/graph-ts' + +export const MockAddresses = [ + '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984', + '0x6b175474e89094c44da98b954eedeac495271d0f', + '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599', + '0xdac17f958d2ee523a2206206994597c13d831ec7', + '0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9', + '0x514910771af9ca656af840dff83e8264ecf986ca', + '0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e', +] + +export const MockBytes = [ + Bytes.fromHexString('0x1f9840a85d5af5bf1d1762f925bdaddc4201f984'), + Bytes.fromHexString('0x6b175474e89094c44da98b954eedeac495271d0f'), + Bytes.fromHexString('0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'), + Bytes.fromHexString('0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'), + Bytes.fromHexString('0x2260fac5e5542a773aa44fbcfedf7c193bc2c599'), + Bytes.fromHexString('0xdac17f958d2ee523a2206206994597c13d831ec7'), + Bytes.fromHexString('0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9'), + Bytes.fromHexString('0x514910771af9ca656af840dff83e8264ecf986ca'), + Bytes.fromHexString('0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e'), +] diff --git a/utils/constants/index.ts b/utils/constants/index.ts index 78840d3..9fb9704 100644 --- a/utils/constants/index.ts +++ b/utils/constants/index.ts @@ -1 +1 @@ -export { ONE_ETHER, ONE_HUNDRED_ETHER } from './wei' +export * from './wei' diff --git a/utils/entities/account.ts b/utils/entities/account.ts index 8054139..2e5f1d5 100644 --- a/utils/entities/account.ts +++ b/utils/entities/account.ts @@ -1,5 +1,10 @@ import { Account } from '../../generated/schema' - +/** + * @notice Find or create an Account entity. + * @dev This function is used to find or create an Account entity. + * @param id The id of the account. + * @returns The Account entity. + */ export function findOrCreateAccount(id: string): Account { const lowerCaseId = id.toLowerCase() let account = Account.load(lowerCaseId) diff --git a/utils/entities/index.ts b/utils/entities/index.ts index 63ef636..2de9953 100644 --- a/utils/entities/index.ts +++ b/utils/entities/index.ts @@ -1,12 +1,6 @@ -export { findOrCreateAccount } from './account' -export { - generateERC721NftId, - upsertERC721Nft, - generateERC1155NftId, - upsertERC1155Nft, - findOrCreateERC1155Nft, -} from './nft' -export { generateRoleAssignmentId, findOrCreateRoleAssignment } from './role-assignment' -export { generateRoleApprovalId, findOrCreateRoleApproval, deleteRoleApproval } from './role-approval' -export { findOrCreateRolesRegistry } from './roles-registry' -export { generateRoleId, findOrCreateRole } from './role' +export * from './account' +export * from './nft' +export * from './role-assignment' +export * from './role-approval' +export * from './roles-registry' +export * from './role' diff --git a/utils/entities/nft/erc-1155.ts b/utils/entities/nft/erc-1155.ts index f266f9d..295e9cb 100644 --- a/utils/entities/nft/erc-1155.ts +++ b/utils/entities/nft/erc-1155.ts @@ -3,10 +3,29 @@ import { Account, Nft } from '../../../generated/schema' import { findOrCreateAccount } from '../account' import { Address, BigInt, store } from '@graphprotocol/graph-ts' +/** + * @notice Generate an ERC1155 NFT id. + * @dev Make sure the owner address is lowercase and it was created/exist as an Account before calling this function. + * @param tokenAddress The token address of the ERC1155 NFT. + * @param tokenId The token id of the ERC1155 NFT. + * @param ownerAddress The owner address of the ERC1155 NFT. + * @returns The ERC1155 NFT id. + */ export function generateERC1155NftId(tokenAddress: string, tokenId: BigInt, ownerAddress: string): string { return tokenAddress + '-' + tokenId.toString() + '-' + ownerAddress } +/** + * @notice Find or create an ERC1155 NFT. + * @dev The id of the ERC1155 NFT is generated using the token address, the token id and the owner address. + * @dev For existing NFTs, the amount is updated for each parties (from and to). + * @param tokenAddress The token address of the ERC1155 NFT. + * @param tokenId The token id of the ERC1155 NFT. + * @param amount The amount of the ERC1155 NFT. + * @param from The previous owner of the ERC1155 NFT. + * @param to The new owner of the ERC1155 NFT. + * @returns The ERC1155 NFT entity created (or found). + */ export function upsertERC1155Nft(tokenAddress: string, tokenId: BigInt, amount: BigInt, from: string, to: string): Nft { const nftId = generateERC1155NftId(tokenAddress, tokenId, to) @@ -22,6 +41,14 @@ export function upsertERC1155Nft(tokenAddress: string, tokenId: BigInt, amount: return updateERC1155Balance(findOrCreateAccount(from), findOrCreateAccount(to), nft, amount) } +/** + * @notice Find or create an ERC1155 NFT. + * @dev The id of the ERC1155 NFT is generated using the token address, the token id and the owner address. + * @param tokenAddress The token address of the ERC1155 NFT. + * @param tokenId The token id of the ERC1155 NFT. + * @param to The owner of the ERC1155 NFT. + * @returns The ERC1155 NFT entity created (or found). + */ export function findOrCreateERC1155Nft(tokenAddress: string, tokenId: BigInt, to: Account): Nft { const nftId = generateERC1155NftId(tokenAddress, tokenId, to.id) @@ -37,16 +64,28 @@ export function findOrCreateERC1155Nft(tokenAddress: string, tokenId: BigInt, to return nft } - +/** + * @notice Update the balance of an ERC1155 NFT. + * @dev from, to and toNft should be created/exist before calling this function. + * @dev If the amount is 0 for either from or to accounts, the NFT is removed. + * @dev If from is the zero address or fromNft does not exist, only toNft is updated. + * @param from The previous owner of the ERC1155 NFT. + * @param to The new owner of the ERC1155 NFT. + * @param toNft The ERC1155 NFT to update. + * @param amount The amount of the ERC1155 NFT. + * @returns The ERC1155 NFT entity updated (or removed). + */ export function updateERC1155Balance(from: Account, to: Account, toNft: Nft, amount: BigInt): Nft { - if (from.id != Address.zero().toHex()) { - const fromNft = findOrCreateERC1155Nft(toNft.tokenAddress, toNft.tokenId, from) + const fromNft = findOrCreateERC1155Nft(toNft.tokenAddress, toNft.tokenId, from) + + if (from.id != Address.zero().toHex() && fromNft) { + const newAmount = fromNft.amount ? fromNft.amount!.minus(amount) : BigInt.zero() // remove if the decudecting amount is 0 - if (!fromNft.amount || fromNft.amount!.minus(amount).equals(BigInt.fromI32(0))) { + if (newAmount.lt(BigInt.fromI32(0)) || newAmount.equals(BigInt.zero())) { store.remove('Nft', fromNft.id) } else { - fromNft.amount = fromNft.amount!.minus(amount) + fromNft.amount = newAmount fromNft.save() } } diff --git a/utils/entities/nft/erc-721.ts b/utils/entities/nft/erc-721.ts index c917c6a..b4350a4 100644 --- a/utils/entities/nft/erc-721.ts +++ b/utils/entities/nft/erc-721.ts @@ -1,11 +1,26 @@ -import { Nft } from '../../../generated/schema' import { NftType } from '../../enums' +import { Nft } from '../../../generated/schema' import { findOrCreateAccount } from '../account' import { BigInt } from '@graphprotocol/graph-ts' +/** + * @notice Generate an ERC721 NFT id. + * @param tokenAddress The token address of the ERC721 NFT. + * @param tokenId The token id of the ERC721 NFT. + * @returns The ERC721 NFT id. + */ export function generateERC721NftId(tokenAddress: string, tokenId: BigInt): string { return tokenAddress + '-' + tokenId.toString() } + +/** + * @notice Find or create an ERC721 NFT. + * @dev The id of the ERC721 NFT is generated using the token address and the token id. + * @param tokenAddress The token address of the ERC721 NFT. + * @param tokenId The token id of the ERC721 NFT. + * @param to The owner of the ERC721 NFT. + * @returns The ERC721 NFT entity created (or found). + */ export function upsertERC721Nft(tokenAddress: string, tokenId: BigInt, to: string): Nft { const nftId = generateERC721NftId(tokenAddress, tokenId) diff --git a/utils/entities/nft/index.ts b/utils/entities/nft/index.ts index 9d43c10..02cf44e 100644 --- a/utils/entities/nft/index.ts +++ b/utils/entities/nft/index.ts @@ -1,2 +1,2 @@ -export { generateERC721NftId, upsertERC721Nft } from './erc-721' -export { generateERC1155NftId, upsertERC1155Nft, findOrCreateERC1155Nft } from './erc-1155' +export * from './erc-721' +export * from './erc-1155' diff --git a/utils/entities/role-approval.ts b/utils/entities/role-approval.ts index b98a77a..8334080 100644 --- a/utils/entities/role-approval.ts +++ b/utils/entities/role-approval.ts @@ -1,6 +1,14 @@ import { Account, RoleApproval, RolesRegistry } from '../../generated/schema' import { store } from '@graphprotocol/graph-ts' - +/** + * @notice Generate a role approval id. + * @dev rolesRegistry, grantor, operator should be created/exist before calling this function. + * @param roleRegistry The roles registry used for the role approval. + * @param grantor The grantor of the role approval. + * @param operator The operator approved by the grantor. + * @param tokenAddress The token address of the role approval. + * @returns The role approval id. + */ export function generateRoleApprovalId( roleRegistry: RolesRegistry, grantor: Account, @@ -10,6 +18,15 @@ export function generateRoleApprovalId( return roleRegistry.id + '-' + grantor.id + '-' + operator.id + '-' + tokenAddress.toLowerCase() } +/** + * @notice Find or create a role approval. + * @dev rolesRegistry, grantor, operator should be created/exist before calling this function. + * @param rolesRegistry The roles registry used for the role approval. + * @param grantor The grantor of the role approval. + * @param operator The operator approved by the grantor. + * @param tokenAddress The token address of the role approval. + * @returns The role approval entity created (or found). + */ export function findOrCreateRoleApproval( rolesRegistry: RolesRegistry, grantor: Account, @@ -25,17 +42,29 @@ export function findOrCreateRoleApproval( roleApproval.operator = operator.id roleApproval.tokenAddress = tokenAddress roleApproval.rolesRegistry = rolesRegistry.id + roleApproval.isApproved = false roleApproval.save() return roleApproval } +/** + * @notice Delete a role approval. + * @dev rolesRegistry, grantor, operator should be created/exist before calling this function. + * @dev If the role approval does not exist, nothing will be done. + * @param rolesRegistry The roles registry used for the role approval. + * @param grantor The grantor of the role approval. + * @param operator The operator approved by the grantor. + * @param tokenAddress The token address of the role approval. + * @returns True if the role approval was deleted, false otherwise. + */ export function deleteRoleApproval( rolesRegistry: RolesRegistry, grantor: Account, operator: Account, tokenAddress: string, -): void { +): boolean { const roleApprovalId = generateRoleApprovalId(rolesRegistry, grantor, operator, tokenAddress) - if (!RoleApproval.load(roleApprovalId)) return + if (!RoleApproval.load(roleApprovalId)) return false store.remove('RoleApproval', roleApprovalId) + return true } diff --git a/utils/entities/role-assignment.ts b/utils/entities/role-assignment.ts index 986276d..75b5c0e 100644 --- a/utils/entities/role-assignment.ts +++ b/utils/entities/role-assignment.ts @@ -4,6 +4,16 @@ import { RoleGranted } from '../../generated/ERC7432/ERC7432' import { findOrCreateRolesRegistry } from './roles-registry' import { findOrCreateRole } from './role' +/** + * @notice Generate a role assignment id. + * @dev roleRegistry, grantor, grantee, nft should be created/exist before calling this function. + * @param roleRegistry The roles registry used for the role assignment. + * @param grantor The grantor of the role assignment. + * @param grantee The grantee of the role assignment. + * @param nft The nft of the role assignment. + * @param roleHash The role hash of the role assignment. + * @returns The role assignment id. + */ export function generateRoleAssignmentId( roleRegistry: RolesRegistry, grantor: Account, @@ -14,19 +24,24 @@ export function generateRoleAssignmentId( return roleRegistry.id + '-' + grantor.id + '-' + grantee.id + '-' + nft.id + '-' + roleHash.toHex() } -export function findOrCreateRoleAssignment( - event: RoleGranted, - grantor: Account, - grantee: Account, - nft: Nft, -): RoleAssignment { +/** + * @notice Upsert a role assignment. + * @dev roleRegistry, grantor, grantee, nft should be created/exist before calling this function. + * @param event The role granted event. + * @param grantor The grantor of the role assignment. + * @param grantee The grantee of the role assignment. + * @param nft The nft of the role assignment. + * @returns The role assignment entity created (or found). + */ +export function upsertRoleAssignment(event: RoleGranted, grantor: Account, grantee: Account, nft: Nft): RoleAssignment { const rolesRegistry = findOrCreateRolesRegistry(event.address.toHex()) const roleAssignmentId = generateRoleAssignmentId(rolesRegistry, grantor, grantee, nft, event.params._role) let roleAssignment = RoleAssignment.load(roleAssignmentId) + const role = findOrCreateRole(rolesRegistry, nft, event.params._role) if (!roleAssignment) { roleAssignment = new RoleAssignment(roleAssignmentId) - roleAssignment.role = findOrCreateRole(rolesRegistry, nft, event.params._role).id + roleAssignment.role = role.id roleAssignment.nft = nft.id roleAssignment.grantor = grantor.id roleAssignment.grantee = grantee.id @@ -37,6 +52,7 @@ export function findOrCreateRoleAssignment( roleAssignment.revocable = event.params._revocable roleAssignment.data = event.params._data roleAssignment.updatedAt = event.block.timestamp + roleAssignment.save() return roleAssignment } diff --git a/utils/entities/role.ts b/utils/entities/role.ts index f0d34ac..0601fb7 100644 --- a/utils/entities/role.ts +++ b/utils/entities/role.ts @@ -1,10 +1,26 @@ -import { Bytes } from '@graphprotocol/graph-ts' +import { BigInt, Bytes } from '@graphprotocol/graph-ts' import { Nft, Role, RolesRegistry } from '../../generated/schema' +/** + * @notice Generate a role id. + * @dev rolesRegistry, nft, roleHash should be created/exist before calling this function. + * @param rolesRegistry The roles registry used for the role. + * @param nft The nft of the role. + * @param roleHash The role hash of the role. + * @returns The role id. + */ export function generateRoleId(rolesRegistry: RolesRegistry, nft: Nft, roleHash: Bytes): string { return rolesRegistry.id + '-' + nft.id + '-' + roleHash.toHex() } +/** + * @notice Find or create a role. + * @dev rolesRegistry, nft, roleHash should be created/exist before calling this function. + * @param rolesRegistry The roles registry used for the role. + * @param nft The nft of the role. + * @param roleHash The role hash of the role. + * @returns The role entity created (or found). + */ export function findOrCreateRole(rolesRegistry: RolesRegistry, nft: Nft, roleHash: Bytes): Role { const roleId = generateRoleId(rolesRegistry, nft, roleHash) let role = Role.load(roleId) @@ -19,3 +35,21 @@ export function findOrCreateRole(rolesRegistry: RolesRegistry, nft: Nft, roleHas return role } + +/** + * @notice Batch find or create a role. + * @dev rolesRegistry, nft, roleHash should be created/exist before calling this function. + * @param rolesRegistry The roles registry used for the role. + * @param nft The nft of the role. + * @param roleHash The role hash of the role. + * @returns The role entity created (or found). + */ +export function findOrCreateRoles(rolesRegistry: RolesRegistry, nft: Nft, roleHashes: Bytes[]): string[] { + const roles: string[] = [] + + for (let i = 0; i < roleHashes.length; i++) { + roles.push(findOrCreateRole(rolesRegistry, nft, roleHashes[i]).id) + } + + return roles +} diff --git a/utils/entities/roles-registry.ts b/utils/entities/roles-registry.ts index 4a286f1..44fd2ba 100644 --- a/utils/entities/roles-registry.ts +++ b/utils/entities/roles-registry.ts @@ -1,5 +1,11 @@ import { RolesRegistry } from '../../generated/schema' +/** + * @notice Find or create a RolesRegistry entity. + * @dev This function is used to find or create a RolesRegistry entity. + * @param rolesRegistryAddress The address of the roles registry. + * @returns The RolesRegistry entity. + */ export function findOrCreateRolesRegistry(rolesRegistryAddress: string): RolesRegistry { let rolesRegistry = RolesRegistry.load(rolesRegistryAddress) diff --git a/utils/enums/index.ts b/utils/enums/index.ts index c993b06..b2df9ec 100644 --- a/utils/enums/index.ts +++ b/utils/enums/index.ts @@ -1 +1 @@ -export { NftType } from './nft' +export * from './nft'