From 785fe0b6f892c3c01e81665e10ac377b514a292b Mon Sep 17 00:00:00 2001 From: Daniel Lima Date: Tue, 12 Dec 2023 17:44:46 -0300 Subject: [PATCH 1/3] ON-553: Refactor handlers --- abis/SftRolesRegistry.json | 586 ++++++++++++++++++ schema.graphql | 17 +- src/{erc1155 => erc-1155}/index.ts | 0 .../transfer-batch-handler.ts | 2 +- .../transfer-single-handler.ts | 2 +- src/erc-721/index.ts | 1 + src/erc-721/transfer-handler.ts | 28 + src/erc-7432/index.ts | 3 + src/erc-7432/role-approval-for-all-handler.ts | 30 + src/erc-7432/role-granted-handler.ts | 61 ++ src/erc-7432/role-revoked-handler.ts | 87 +++ src/erc721/index.ts | 18 - src/erc7432/index.ts | 3 - src/erc7432/role/role-approval-handler.ts | 35 -- src/sftRolesRegistry/index.ts | 3 + .../role-approval-for-all-handler.ts | 30 + .../role-granted-handler.ts} | 6 +- .../role-revoked-handler.ts} | 6 +- subgraph.template.yaml | 37 +- .../transfer-batch-handler.test.ts | 4 +- .../transfer-single-handler.test.ts | 4 +- tests/erc-721/transfer-handler.test.ts | 103 +++ .../approval-handler.test.ts | 27 +- .../grant-handler.test.ts | 74 ++- .../revoke-handler.test.ts | 50 +- tests/erc721/transfer-handler.test.ts | 61 -- tests/helpers/assertion.ts | 59 ++ tests/helpers/events.ts | 156 ++--- tests/{helpers => mocks}/entities.ts | 50 +- tests/mocks/events.ts | 180 ++++++ tests/mocks/values.ts | 25 + utils/constants/index.ts | 2 +- utils/entities/account.ts | 7 +- utils/entities/helper-nft-ownership.ts | 32 + utils/entities/index.ts | 19 +- utils/entities/nft/erc-1155.ts | 49 +- utils/entities/nft/erc-721.ts | 17 +- utils/entities/nft/index.ts | 4 +- utils/entities/role-approval.ts | 35 +- utils/entities/role-assignment.ts | 38 +- utils/entities/role.ts | 37 +- utils/entities/roles-registry.ts | 6 + utils/enums/index.ts | 2 +- 43 files changed, 1633 insertions(+), 363 deletions(-) create mode 100644 abis/SftRolesRegistry.json rename src/{erc1155 => erc-1155}/index.ts (100%) rename src/{erc1155 => erc-1155}/transfer-batch-handler.ts (91%) rename src/{erc1155 => erc-1155}/transfer-single-handler.ts (89%) create mode 100644 src/erc-721/index.ts create mode 100644 src/erc-721/transfer-handler.ts create mode 100644 src/erc-7432/index.ts create mode 100644 src/erc-7432/role-approval-for-all-handler.ts create mode 100644 src/erc-7432/role-granted-handler.ts create mode 100644 src/erc-7432/role-revoked-handler.ts delete mode 100644 src/erc721/index.ts delete mode 100644 src/erc7432/index.ts delete mode 100644 src/erc7432/role/role-approval-handler.ts create mode 100644 src/sftRolesRegistry/index.ts create mode 100644 src/sftRolesRegistry/role-approval-for-all-handler.ts rename src/{erc7432/role/grant-handler.ts => sftRolesRegistry/role-granted-handler.ts} (87%) rename src/{erc7432/role/revoke-handler.ts => sftRolesRegistry/role-revoked-handler.ts} (90%) rename tests/{erc1155 => erc-1155}/transfer-batch-handler.test.ts (97%) rename tests/{erc1155 => erc-1155}/transfer-single-handler.test.ts (97%) create mode 100644 tests/erc-721/transfer-handler.test.ts rename tests/{erc7432 => erc-7432}/approval-handler.test.ts (67%) rename tests/{erc7432 => erc-7432}/grant-handler.test.ts (76%) rename tests/{erc7432 => erc-7432}/revoke-handler.test.ts (76%) delete mode 100644 tests/erc721/transfer-handler.test.ts create mode 100644 tests/helpers/assertion.ts rename tests/{helpers => mocks}/entities.ts (59%) create mode 100644 tests/mocks/events.ts create mode 100644 tests/mocks/values.ts create mode 100644 utils/entities/helper-nft-ownership.ts diff --git a/abis/SftRolesRegistry.json b/abis/SftRolesRegistry.json new file mode 100644 index 0000000..2b43383 --- /dev/null +++ b/abis/SftRolesRegistry.json @@ -0,0 +1,586 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_tokenAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "_operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "_isApproved", + "type": "bool" + } + ], + "name": "RoleApprovalForAll", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "_nonce", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "_role", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "address", + "name": "_tokenAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_tokenId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_tokenAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "_grantor", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "_grantee", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "_expirationDate", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "bool", + "name": "_revocable", + "type": "bool" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "_nonce", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "_role", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "address", + "name": "_tokenAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_tokenId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_tokenAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "_revoker", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "_grantee", + "type": "address" + } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "_nonce", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "_grantor", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "_tokenAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_tokenId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_tokenAmount", + "type": "uint256" + } + ], + "name": "Withdrew", + "type": "event" + }, + { + "inputs": [], + "name": "UNIQUE_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "deposits", + "outputs": [ + { + "internalType": "address", + "name": "grantor", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "tokenAmount", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "tokenAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "grantor", + "type": "address" + }, + { + "internalType": "address", + "name": "grantee", + "type": "address" + }, + { + "internalType": "uint64", + "name": "expirationDate", + "type": "uint64" + }, + { + "internalType": "bool", + "name": "revocable", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct IERCXXXX.RoleAssignment", + "name": "_grantRoleData", + "type": "tuple" + } + ], + "name": "grantRoleFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_tokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "_grantor", + "type": "address" + }, + { + "internalType": "address", + "name": "_operator", + "type": "address" + } + ], + "name": "isRoleApprovedForAll", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "onERC1155BatchReceived", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "onERC1155Received", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_nonce", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "_role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "_grantee", + "type": "address" + } + ], + "name": "revokeRoleFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_nonce", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "_role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "_grantee", + "type": "address" + } + ], + "name": "roleData", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "grantee", + "type": "address" + }, + { + "internalType": "uint64", + "name": "expirationDate", + "type": "uint64" + }, + { + "internalType": "bool", + "name": "revocable", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct IERCXXXX.RoleData", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_nonce", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "_role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "_grantee", + "type": "address" + } + ], + "name": "roleExpirationDate", + "outputs": [ + { + "internalType": "uint64", + "name": "expirationDate_", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_tokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "_operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "_isApproved", + "type": "bool" + } + ], + "name": "setRoleApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "tokenApprovals", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_nonce", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/schema.graphql b/schema.graphql index 8140c10..ac418f1 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,10 +35,11 @@ type RoleAssignment @entity { } type Role @entity { - id: ID! # rolesRegistryAddress + tokenId + tokenAddress + roleHash + id: ID! # rolesRegistryAddress + tokenAddress + tokenId + roleHash rolesRegistry: RolesRegistry! roleHash: Bytes! nft: Nft! + lastNonRevocableExpirationDate: BigInt! roleAssignments: [RoleAssignment!] @derivedFrom(field: "role") } @@ -48,6 +49,7 @@ type RoleApproval @entity { grantor: Account! operator: Account! tokenAddress: String! + isApproved: Boolean! } type RolesRegistry @entity { @@ -55,3 +57,12 @@ type RolesRegistry @entity { roles: [Role!] @derivedFrom(field: "rolesRegistry") roleApprovals: [RoleApproval!] @derivedFrom(field: "rolesRegistry") } + +# Helper Entities + +type HelperNftOwnership @entity { + id: ID! # tokenAddress + tokenId + owner + nft: Nft! + originalOwner: Account! + isSameOwner: Boolean! +} \ 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..9544f63 --- /dev/null +++ b/src/erc-721/transfer-handler.ts @@ -0,0 +1,28 @@ +import { Transfer } from '../../generated/ERC721/ERC721' +import { upsertERC721Nft, upsertHelperNftOwnership } 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() + + const nft = upsertERC721Nft(tokenAddress, tokenId, to) + upsertHelperNftOwnership(nft, from) + upsertHelperNftOwnership(nft, 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..20d4328 --- /dev/null +++ b/src/erc-7432/role-revoked-handler.ts @@ -0,0 +1,87 @@ +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() + + if (!roleAssignment.revocable) { + // smart contract validate that if a role is not revocable, it can only be revoked by the grantee + // in that case, we can set the lastNonRevocableExpirationDate to zero, assuming that the grantee is revoking its own role + const role = findOrCreateRole(rolesRegistry, nft, event.params._role) + role.lastNonRevocableExpirationDate = BigInt.zero() + role.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/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/src/sftRolesRegistry/index.ts b/src/sftRolesRegistry/index.ts new file mode 100644 index 0000000..34daa46 --- /dev/null +++ b/src/sftRolesRegistry/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/sftRolesRegistry/role-approval-for-all-handler.ts b/src/sftRolesRegistry/role-approval-for-all-handler.ts new file mode 100644 index 0000000..af518ff --- /dev/null +++ b/src/sftRolesRegistry/role-approval-for-all-handler.ts @@ -0,0 +1,30 @@ +import { RoleApprovalForAll } from '../../generated/SftRolesRegistry/SftRolesRegistry' +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('[sftRolesRegistry][handleRoleApprovalForAll] Updated RoleAssignment Approval: {} Tx: {}', [ + roleApproval.id, + event.transaction.hash.toHex(), + ]) +} diff --git a/src/erc7432/role/grant-handler.ts b/src/sftRolesRegistry/role-granted-handler.ts similarity index 87% rename from src/erc7432/role/grant-handler.ts rename to src/sftRolesRegistry/role-granted-handler.ts index 20a7bc8..5a173fc 100644 --- a/src/erc7432/role/grant-handler.ts +++ b/src/sftRolesRegistry/role-granted-handler.ts @@ -1,7 +1,7 @@ import { log } from '@graphprotocol/graph-ts' -import { RoleGranted } from '../../../generated/ERC7432/ERC7432' -import { Account, Nft } from '../../../generated/schema' -import { generateERC721NftId, findOrCreateAccount, findOrCreateRoleAssignment } from '../../../utils' +import { RoleGranted } from '../../generated/SftRolesRegistry/SftRolesRegistry' +import { Account, Nft } from '../../generated/schema' +import { generateERC721NftId, findOrCreateAccount, findOrCreateRoleAssignment } from '../../utils' export function handleRoleGranted(event: RoleGranted): void { const tokenId = event.params._tokenId diff --git a/src/erc7432/role/revoke-handler.ts b/src/sftRolesRegistry/role-revoked-handler.ts similarity index 90% rename from src/erc7432/role/revoke-handler.ts rename to src/sftRolesRegistry/role-revoked-handler.ts index 75e3758..6f8bd99 100644 --- a/src/erc7432/role/revoke-handler.ts +++ b/src/sftRolesRegistry/role-revoked-handler.ts @@ -1,7 +1,7 @@ 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' +import { RoleRevoked } from '../../generated/SftRolesRegistry/SftRolesRegistry' +import { Account, Nft, RoleAssignment } from '../../generated/schema' +import { findOrCreateRolesRegistry, generateERC721NftId, generateRoleAssignmentId } from '../../utils' export function handleRoleRevoked(event: RoleRevoked): void { const tokenId = event.params._tokenId diff --git a/subgraph.template.yaml b/subgraph.template.yaml index 711c5ed..4a0fb89 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,35 @@ dataSources: handler: handleRoleRevoked - event: RoleApprovalForAll(indexed address,indexed address,bool) handler: handleRoleApprovalForAll - file: ./src/erc7432/index.ts + file: ./src/erc-7432/index.ts + + - name: SftRolesRegistry + kind: ethereum + network: {{network}} + source: + abi: SftRolesRegistry +{{#isSelfHosted}} + startBlock: 1 +{{/isSelfHosted}} + mapping: + kind: ethereum/events + apiVersion: 0.0.6 + language: wasm/assemblyscript + entities: + - Nft + - Account + - RoleAssignment + - RoleApproval + abis: + - name: SftRolesRegistry + file: ./abis/SftRolesRegistry.json + eventHandlers: + - event: RoleGranted(indexed uint256,indexed bytes32,address,uint256,uint256,address,address,uint64,bool,bytes) + handler: handleRoleGranted + - event: RoleRevoked(indexed uint256,indexed bytes32,address,uint256,uint256,address,address) + handler: handleRoleRevoked + - event: RoleApprovalForAll(indexed address,indexed address,bool) + handler: handleRoleApprovalForAll + - event: Withdrew(indexed uint256,indexed address,address,uint256,uint256) + handler: handleWithdrew + file: ./src/erc-7432/index.ts 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/erc-721/transfer-handler.test.ts b/tests/erc-721/transfer-handler.test.ts new file mode 100644 index 0000000..65e56bc --- /dev/null +++ b/tests/erc-721/transfer-handler.test.ts @@ -0,0 +1,103 @@ +import { assert, describe, test, clearStore, afterAll, beforeEach, afterEach } from 'matchstick-as' +import { BigInt } from '@graphprotocol/graph-ts' +import { handleTransfer } from '../../src/erc-721' +import { NftType, generateERC721NftId, generateHelperNftOwnershipId, upsertHelperNftOwnership } from '../../utils' +import { createTransferEvent } from '../mocks/events' +import { Addresses, ZERO_ADDRESS } from '../helpers/contants' +import { MockAddresses } from '../mocks/values' +import { Nft } from '../../generated/schema' + +const originalOwnerAddress = MockAddresses[0] +const newOwnerAddress = MockAddresses[1] +const tokenAddress = MockAddresses[2] +const tokenId = BigInt.fromI32(1) +const nftId = generateERC721NftId(tokenAddress, tokenId) + +let nft: Nft + +describe('ERC-721 Transfer Handler', () => { + afterAll(() => { + clearStore() + }) + + test('should create NFT and Account when NFT and Account does not exist', () => { + assert.entityCount('Nft', 0) + assert.entityCount('Account', 0) + + const event = createTransferEvent(Addresses[0], Addresses[1], tokenId.toString(), ZERO_ADDRESS) + handleTransfer(event) + + assert.entityCount('Nft', 1) + assert.entityCount('Account', 1) + + const _id = generateERC721NftId(event.address.toHexString(), event.params.tokenId) + assert.fieldEquals('Nft', _id, 'tokenAddress', ZERO_ADDRESS) + assert.fieldEquals('Nft', _id, 'tokenId', tokenId.toString()) + assert.fieldEquals('Nft', _id, 'owner', Addresses[1]) + }) + + test('should transfer NFT and create Account when NFT exist Account does not', () => { + assert.entityCount('Nft', 1) + assert.entityCount('Account', 1) + + const event = createTransferEvent(Addresses[1], Addresses[2], tokenId.toString(), ZERO_ADDRESS) + handleTransfer(event) + + assert.entityCount('Nft', 1) + assert.entityCount('Account', 2) + + const _id = generateERC721NftId(event.address.toHexString(), event.params.tokenId) + assert.fieldEquals('Nft', _id, 'tokenAddress', ZERO_ADDRESS) + assert.fieldEquals('Nft', _id, 'tokenId', tokenId.toString()) + assert.fieldEquals('Nft', _id, 'owner', Addresses[2]) + }) + + test('should only transfer NFT when NFT and Account exist', () => { + assert.entityCount('Nft', 1) + assert.entityCount('Account', 2) + + const event = createTransferEvent(Addresses[0], Addresses[2], tokenId.toString(), ZERO_ADDRESS) + handleTransfer(event) + + assert.entityCount('Nft', 1) + assert.entityCount('Account', 2) + + const _id = generateERC721NftId(event.address.toHexString(), event.params.tokenId) + assert.fieldEquals('Nft', _id, 'tokenAddress', ZERO_ADDRESS) + assert.fieldEquals('Nft', _id, 'tokenId', tokenId.toString()) + assert.fieldEquals('Nft', _id, 'owner', Addresses[2]) + }) +}) + +describe('HelperNftOwnership', () => { + beforeEach(() => { + nft = new Nft(nftId) + nft.tokenAddress = tokenAddress + nft.tokenId = tokenId + nft.owner = originalOwnerAddress + nft.type = NftType.ERC721 + nft.save() + }) + + test('Should change "isSameOwner" to false when NFT is transferred', () => { + const helperNftOwnership = upsertHelperNftOwnership(nft, nft.owner) + assert.entityCount('HelperNftOwnership', 1) + assert.fieldEquals('HelperNftOwnership', helperNftOwnership.id, 'isSameOwner', 'true') + + const event = createTransferEvent(originalOwnerAddress, newOwnerAddress, tokenId.toString(), tokenAddress) + handleTransfer(event) + + assert.entityCount('HelperNftOwnership', 2) + assert.fieldEquals('HelperNftOwnership', helperNftOwnership.id, 'isSameOwner', 'false') + assert.fieldEquals( + 'HelperNftOwnership', + generateHelperNftOwnershipId(tokenAddress, tokenId, newOwnerAddress), + 'isSameOwner', + 'true', + ) + }) + + afterEach(() => { + clearStore() + }) +}) 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 76% rename from tests/erc7432/grant-handler.test.ts rename to tests/erc-7432/grant-handler.test.ts index 616c0a2..601ee7d 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] @@ -142,6 +143,7 @@ describe('ERC-7432 RoleGranted Handler', () => { expirationDate, data, event1.address.toHex(), + BigInt.zero(), ) validateRole( grantorAccount, @@ -151,6 +153,7 @@ describe('ERC-7432 RoleGranted Handler', () => { expirationDate, data, event2.address.toHex(), + BigInt.zero(), ) validateRole( grantorAccount, @@ -160,6 +163,7 @@ describe('ERC-7432 RoleGranted Handler', () => { expirationDate, data, event3.address.toHex(), + BigInt.zero(), ) }) @@ -214,8 +218,66 @@ describe('ERC-7432 RoleGranted Handler', () => { assert.entityCount('Account', 3) const grantorAccount = new Account(grantor) - validateRole(grantorAccount, new Account(Addresses[0]), nft1, RoleAssignmentId, expirationDate, data, rolesRegistry) - validateRole(grantorAccount, new Account(Addresses[1]), nft2, RoleAssignmentId, expirationDate, data, rolesRegistry) - validateRole(grantorAccount, new Account(Addresses[2]), nft3, RoleAssignmentId, expirationDate, data, rolesRegistry) + validateRole( + grantorAccount, + new Account(Addresses[0]), + nft1, + RoleAssignmentId, + expirationDate, + data, + rolesRegistry, + BigInt.zero(), + ) + validateRole( + grantorAccount, + new Account(Addresses[1]), + nft2, + RoleAssignmentId, + expirationDate, + data, + rolesRegistry, + BigInt.zero(), + ) + validateRole( + grantorAccount, + new Account(Addresses[2]), + nft3, + RoleAssignmentId, + expirationDate, + data, + rolesRegistry, + BigInt.zero(), + ) + }) + + test('should update lastNonRevocableExpirationDate when revocable is false', () => { + const nft = createMockNft(tokenAddress, tokenId, grantor) + assert.entityCount('RoleAssignment', 0) + assert.entityCount('Role', 0) + assert.entityCount('Account', 1) + + const event1 = createNewRoleGrantedEvent( + RoleAssignmentId, + tokenId, + tokenAddress, + Addresses[0], + grantor, + expirationDate, + false, + data, + ) + handleRoleGranted(event1) + + const grantorAccount = new Account(grantor) + validateRole( + grantorAccount, + new Account(Addresses[0]), + nft, + RoleAssignmentId, + expirationDate, + data, + event1.address.toHex(), + expirationDate, + ) }) }) diff --git a/tests/erc7432/revoke-handler.test.ts b/tests/erc-7432/revoke-handler.test.ts similarity index 76% rename from tests/erc7432/revoke-handler.test.ts rename to tests/erc-7432/revoke-handler.test.ts index 5edaca0..fac323e 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 { findOrCreateRole, findOrCreateRolesRegistry, generateERC721NftId, generateRoleAssignmentId } from '../../utils' import { Account, Nft } from '../../generated/schema' +import { validateRole } from '../helpers/assertion' const tokenId = '123' const RoleAssignmentId = Bytes.fromUTF8('0xGrantRole') @@ -105,9 +106,9 @@ describe('ERC-7432 RoleRevoked Handler', () => { assert.entityCount('RoleAssignment', 3) assert.entityCount('Role', 1) const revokerAccount = new Account(revoker) - validateRole(revokerAccount, account1, nft, RoleAssignmentId, ONE, data, rolesRegistry) - validateRole(revokerAccount, account2, nft, RoleAssignmentId, ONE, data, rolesRegistry) - validateRole(revokerAccount, account3, nft, RoleAssignmentId, ONE, data, rolesRegistry) + validateRole(revokerAccount, account1, nft, RoleAssignmentId, ONE, data, rolesRegistry, BigInt.zero()) + validateRole(revokerAccount, account2, nft, RoleAssignmentId, ONE, data, rolesRegistry, BigInt.zero()) + validateRole(revokerAccount, account3, nft, RoleAssignmentId, ONE, data, rolesRegistry, BigInt.zero()) }) test('should revoke multiple roles for different NFTs', () => { @@ -133,8 +134,37 @@ describe('ERC-7432 RoleRevoked Handler', () => { assert.entityCount('RoleAssignment', 3) assert.entityCount('Role', 3) const revokerAccount = new Account(revoker) - validateRole(revokerAccount, granteeAccount, nft1, RoleAssignmentId, ONE, data, rolesRegistry) - validateRole(revokerAccount, granteeAccount, nft2, RoleAssignmentId, ONE, data, rolesRegistry) - validateRole(revokerAccount, granteeAccount, nft3, RoleAssignmentId, ONE, data, rolesRegistry) + validateRole(revokerAccount, granteeAccount, nft1, RoleAssignmentId, ONE, data, rolesRegistry, BigInt.zero()) + validateRole(revokerAccount, granteeAccount, nft2, RoleAssignmentId, ONE, data, rolesRegistry, BigInt.zero()) + validateRole(revokerAccount, granteeAccount, nft3, RoleAssignmentId, ONE, data, rolesRegistry, BigInt.zero()) + }) + + test('should update lastNonRevocableExpirationDate when grantee is revoking a non revocable role', () => { + const nft = createMockNft(tokenAddress, tokenId, revoker) + const granteeAccount = createMockAccount(grantee) + const roleAssignment = createMockRoleAssignment( + RoleAssignmentId, + revoker, + grantee, + nft, + expirationDate, + rolesRegistry, + ) + roleAssignment.revocable = false + roleAssignment.save() + const role = findOrCreateRole(findOrCreateRolesRegistry(rolesRegistry), nft, RoleAssignmentId) + role.lastNonRevocableExpirationDate = expirationDate + role.save() + assert.entityCount('RoleAssignment', 1) + assert.entityCount('Role', 1) + assert.fieldEquals('Role', role.id, 'lastNonRevocableExpirationDate', expirationDate.toString()) + + const event = createNewRoleRevokedEvent(RoleAssignmentId, nft, revoker, grantee) + handleRoleRevoked(event) + + assert.entityCount('RoleAssignment', 1) + assert.entityCount('Role', 1) + const revokerAccount = new Account(revoker) + validateRole(revokerAccount, granteeAccount, nft, RoleAssignmentId, ONE, data, rolesRegistry, BigInt.zero()) }) }) diff --git a/tests/erc721/transfer-handler.test.ts b/tests/erc721/transfer-handler.test.ts deleted file mode 100644 index fda360c..0000000 --- a/tests/erc721/transfer-handler.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { assert, describe, test, clearStore, afterAll } from 'matchstick-as' -import { handleTransfer } from '../../src/erc721' -import { generateERC721NftId } from '../../utils' -import { createTransferEvent } from '../helpers/events' -import { Addresses, ZERO_ADDRESS } from '../helpers/contants' - -const tokenId = '123' - -describe('ERC-721 Transfer Handler', () => { - afterAll(() => { - clearStore() - }) - - test('should create NFT and Account when NFT and Account does not exist', () => { - assert.entityCount('Nft', 0) - assert.entityCount('Account', 0) - - const event = createTransferEvent(Addresses[0], Addresses[1], tokenId, ZERO_ADDRESS) - handleTransfer(event) - - assert.entityCount('Nft', 1) - assert.entityCount('Account', 1) - - 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, 'owner', Addresses[1]) - }) - - test('should transfer NFT and create Account when NFT exist Account does not', () => { - assert.entityCount('Nft', 1) - assert.entityCount('Account', 1) - - const event = createTransferEvent(Addresses[1], Addresses[2], tokenId, ZERO_ADDRESS) - handleTransfer(event) - - assert.entityCount('Nft', 1) - assert.entityCount('Account', 2) - - 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, 'owner', Addresses[2]) - }) - - test('should only transfer NFT when NFT and Account exist', () => { - assert.entityCount('Nft', 1) - assert.entityCount('Account', 2) - - const event = createTransferEvent(Addresses[0], Addresses[2], tokenId, ZERO_ADDRESS) - handleTransfer(event) - - assert.entityCount('Nft', 1) - assert.entityCount('Account', 2) - - 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, 'owner', Addresses[2]) - }) -}) diff --git a/tests/helpers/assertion.ts b/tests/helpers/assertion.ts new file mode 100644 index 0000000..aa4ea30 --- /dev/null +++ b/tests/helpers/assertion.ts @@ -0,0 +1,59 @@ +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, + lastNonRevocableExpirationDate: BigInt, +): 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) + assert.fieldEquals('Role', roleId, 'lastNonRevocableExpirationDate', lastNonRevocableExpirationDate.toString()) + + 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..17515ea 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))) @@ -43,6 +42,7 @@ export function createMockRoleAssignment( role.roleHash = roleHash role.nft = nft.id role.rolesRegistry = rolesRegistryAddress + role.lastNonRevocableExpirationDate = BigInt.zero() role.save() const roleAssignmentId = generateRoleAssignmentId( @@ -71,6 +71,7 @@ export function createMockRoleApproval( operator: string, tokenAddress: string, rolesRegistryAddress: string, + isApproved: boolean, ): RoleApproval { const rolesRegistry = findOrCreateRolesRegistry(rolesRegistryAddress) const roleApprovalId = generateRoleApprovalId( @@ -84,52 +85,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/helper-nft-ownership.ts b/utils/entities/helper-nft-ownership.ts new file mode 100644 index 0000000..8bd9cd7 --- /dev/null +++ b/utils/entities/helper-nft-ownership.ts @@ -0,0 +1,32 @@ +import { HelperNftOwnership, Nft } from '../../generated/schema' +import { BigInt } from '@graphprotocol/graph-ts' + +/** + @notice Generate HelperNftOwnership id. + @dev The id of the HelperNftOwnership is generated using the token address, the token id, and the owner address. + * @param tokenAddress The token address of the ERC721 NFT. + * @param tokenId The token id of the ERC721 NFT. + * @param owner The owner of the ERC721 NFT. + */ +export function generateHelperNftOwnershipId(tokenAddress: string, tokenId: BigInt, owner: string): string { + return tokenAddress + tokenId.toString() + owner +} + +/** + * @notice Insert or update an HelperNftOwnership. + * @dev It updates the "isSameOwner" as a way to check if the owner is the same. + * @param nft The nft of the given entity. + * @param owner The owner to be checked. + */ +export function upsertHelperNftOwnership(nft: Nft, owner: string): HelperNftOwnership { + const helperNftOwnershipId = generateHelperNftOwnershipId(nft.tokenAddress, nft.tokenId, owner) + let helperNftOwnership = HelperNftOwnership.load(helperNftOwnershipId) + if (!helperNftOwnership) { + helperNftOwnership = new HelperNftOwnership(helperNftOwnershipId) + helperNftOwnership.nft = nft.id + helperNftOwnership.originalOwner = nft.owner + } + helperNftOwnership.isSameOwner = nft.owner == owner + helperNftOwnership.save() + return helperNftOwnership +} diff --git a/utils/entities/index.ts b/utils/entities/index.ts index 63ef636..f8c58a9 100644 --- a/utils/entities/index.ts +++ b/utils/entities/index.ts @@ -1,12 +1,7 @@ -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' +export * from './helper-nft-ownership' 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..bbb5caa 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,15 @@ export function findOrCreateRoleAssignment( roleAssignment.revocable = event.params._revocable roleAssignment.data = event.params._data roleAssignment.updatedAt = event.block.timestamp + + if (!roleAssignment.revocable) { + // if the role is not revocable, we update the lastNonRevocableExpirationDate + // since the grantor will not able to revoke the role before this date + // this is useful for the revocation check + role.lastNonRevocableExpirationDate = event.params._expirationDate + role.save() + } + roleAssignment.save() return roleAssignment } diff --git a/utils/entities/role.ts b/utils/entities/role.ts index f0d34ac..c75df10 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) @@ -14,8 +30,27 @@ export function findOrCreateRole(rolesRegistry: RolesRegistry, nft: Nft, roleHas role.roleHash = roleHash role.nft = nft.id role.rolesRegistry = rolesRegistry.id + role.lastNonRevocableExpirationDate = BigInt.zero() role.save() } 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' From 19f4c149c9fb15f4fa603b403a92037f0f82741c Mon Sep 17 00:00:00 2001 From: Daniel Lima Date: Tue, 12 Dec 2023 17:46:08 -0300 Subject: [PATCH 2/3] ON-553: refactor --- abis/SftRolesRegistry.json | 586 ------------------ src/sftRolesRegistry/index.ts | 3 - .../role-approval-for-all-handler.ts | 30 - src/sftRolesRegistry/role-granted-handler.ts | 35 -- src/sftRolesRegistry/role-revoked-handler.ts | 49 -- subgraph.template.yaml | 33 +- 6 files changed, 1 insertion(+), 735 deletions(-) delete mode 100644 abis/SftRolesRegistry.json delete mode 100644 src/sftRolesRegistry/index.ts delete mode 100644 src/sftRolesRegistry/role-approval-for-all-handler.ts delete mode 100644 src/sftRolesRegistry/role-granted-handler.ts delete mode 100644 src/sftRolesRegistry/role-revoked-handler.ts diff --git a/abis/SftRolesRegistry.json b/abis/SftRolesRegistry.json deleted file mode 100644 index 2b43383..0000000 --- a/abis/SftRolesRegistry.json +++ /dev/null @@ -1,586 +0,0 @@ -[ - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "_tokenAddress", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "_operator", - "type": "address" - }, - { - "indexed": false, - "internalType": "bool", - "name": "_isApproved", - "type": "bool" - } - ], - "name": "RoleApprovalForAll", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint256", - "name": "_nonce", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "bytes32", - "name": "_role", - "type": "bytes32" - }, - { - "indexed": false, - "internalType": "address", - "name": "_tokenAddress", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_tokenId", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_tokenAmount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "address", - "name": "_grantor", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "_grantee", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint64", - "name": "_expirationDate", - "type": "uint64" - }, - { - "indexed": false, - "internalType": "bool", - "name": "_revocable", - "type": "bool" - }, - { - "indexed": false, - "internalType": "bytes", - "name": "_data", - "type": "bytes" - } - ], - "name": "RoleGranted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint256", - "name": "_nonce", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "bytes32", - "name": "_role", - "type": "bytes32" - }, - { - "indexed": false, - "internalType": "address", - "name": "_tokenAddress", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_tokenId", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_tokenAmount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "address", - "name": "_revoker", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "_grantee", - "type": "address" - } - ], - "name": "RoleRevoked", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint256", - "name": "_nonce", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "address", - "name": "_grantor", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "_tokenAddress", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_tokenId", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_tokenAmount", - "type": "uint256" - } - ], - "name": "Withdrew", - "type": "event" - }, - { - "inputs": [], - "name": "UNIQUE_ROLE", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "name": "deposits", - "outputs": [ - { - "internalType": "address", - "name": "grantor", - "type": "address" - }, - { - "internalType": "address", - "name": "tokenAddress", - "type": "address" - }, - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "tokenAmount", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "uint256", - "name": "nonce", - "type": "uint256" - }, - { - "internalType": "bytes32", - "name": "role", - "type": "bytes32" - }, - { - "internalType": "address", - "name": "tokenAddress", - "type": "address" - }, - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "tokenAmount", - "type": "uint256" - }, - { - "internalType": "address", - "name": "grantor", - "type": "address" - }, - { - "internalType": "address", - "name": "grantee", - "type": "address" - }, - { - "internalType": "uint64", - "name": "expirationDate", - "type": "uint64" - }, - { - "internalType": "bool", - "name": "revocable", - "type": "bool" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "internalType": "struct IERCXXXX.RoleAssignment", - "name": "_grantRoleData", - "type": "tuple" - } - ], - "name": "grantRoleFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_tokenAddress", - "type": "address" - }, - { - "internalType": "address", - "name": "_grantor", - "type": "address" - }, - { - "internalType": "address", - "name": "_operator", - "type": "address" - } - ], - "name": "isRoleApprovedForAll", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "uint256[]", - "name": "", - "type": "uint256[]" - }, - { - "internalType": "uint256[]", - "name": "", - "type": "uint256[]" - }, - { - "internalType": "bytes", - "name": "", - "type": "bytes" - } - ], - "name": "onERC1155BatchReceived", - "outputs": [ - { - "internalType": "bytes4", - "name": "", - "type": "bytes4" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "uint256", - "name": "", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "", - "type": "bytes" - } - ], - "name": "onERC1155Received", - "outputs": [ - { - "internalType": "bytes4", - "name": "", - "type": "bytes4" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_nonce", - "type": "uint256" - }, - { - "internalType": "bytes32", - "name": "_role", - "type": "bytes32" - }, - { - "internalType": "address", - "name": "_grantee", - "type": "address" - } - ], - "name": "revokeRoleFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_nonce", - "type": "uint256" - }, - { - "internalType": "bytes32", - "name": "_role", - "type": "bytes32" - }, - { - "internalType": "address", - "name": "_grantee", - "type": "address" - } - ], - "name": "roleData", - "outputs": [ - { - "components": [ - { - "internalType": "address", - "name": "grantee", - "type": "address" - }, - { - "internalType": "uint64", - "name": "expirationDate", - "type": "uint64" - }, - { - "internalType": "bool", - "name": "revocable", - "type": "bool" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "internalType": "struct IERCXXXX.RoleData", - "name": "", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_nonce", - "type": "uint256" - }, - { - "internalType": "bytes32", - "name": "_role", - "type": "bytes32" - }, - { - "internalType": "address", - "name": "_grantee", - "type": "address" - } - ], - "name": "roleExpirationDate", - "outputs": [ - { - "internalType": "uint64", - "name": "expirationDate_", - "type": "uint64" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_tokenAddress", - "type": "address" - }, - { - "internalType": "address", - "name": "_operator", - "type": "address" - }, - { - "internalType": "bool", - "name": "_isApproved", - "type": "bool" - } - ], - "name": "setRoleApprovalForAll", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes4", - "name": "interfaceId", - "type": "bytes4" - } - ], - "name": "supportsInterface", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "tokenApprovals", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_nonce", - "type": "uint256" - } - ], - "name": "withdraw", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] \ No newline at end of file diff --git a/src/sftRolesRegistry/index.ts b/src/sftRolesRegistry/index.ts deleted file mode 100644 index 34daa46..0000000 --- a/src/sftRolesRegistry/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { handleRoleGranted } from './role-granted-handler' -export { handleRoleRevoked } from './role-revoked-handler' -export { handleRoleApprovalForAll } from './role-approval-for-all-handler' diff --git a/src/sftRolesRegistry/role-approval-for-all-handler.ts b/src/sftRolesRegistry/role-approval-for-all-handler.ts deleted file mode 100644 index af518ff..0000000 --- a/src/sftRolesRegistry/role-approval-for-all-handler.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { RoleApprovalForAll } from '../../generated/SftRolesRegistry/SftRolesRegistry' -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('[sftRolesRegistry][handleRoleApprovalForAll] Updated RoleAssignment Approval: {} Tx: {}', [ - roleApproval.id, - event.transaction.hash.toHex(), - ]) -} diff --git a/src/sftRolesRegistry/role-granted-handler.ts b/src/sftRolesRegistry/role-granted-handler.ts deleted file mode 100644 index 5a173fc..0000000 --- a/src/sftRolesRegistry/role-granted-handler.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { log } from '@graphprotocol/graph-ts' -import { RoleGranted } from '../../generated/SftRolesRegistry/SftRolesRegistry' -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/sftRolesRegistry/role-revoked-handler.ts b/src/sftRolesRegistry/role-revoked-handler.ts deleted file mode 100644 index 6f8bd99..0000000 --- a/src/sftRolesRegistry/role-revoked-handler.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { log } from '@graphprotocol/graph-ts' -import { RoleRevoked } from '../../generated/SftRolesRegistry/SftRolesRegistry' -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/subgraph.template.yaml b/subgraph.template.yaml index 4a0fb89..9ad5d19 100644 --- a/subgraph.template.yaml +++ b/subgraph.template.yaml @@ -82,35 +82,4 @@ dataSources: handler: handleRoleRevoked - event: RoleApprovalForAll(indexed address,indexed address,bool) handler: handleRoleApprovalForAll - file: ./src/erc-7432/index.ts - - - name: SftRolesRegistry - kind: ethereum - network: {{network}} - source: - abi: SftRolesRegistry -{{#isSelfHosted}} - startBlock: 1 -{{/isSelfHosted}} - mapping: - kind: ethereum/events - apiVersion: 0.0.6 - language: wasm/assemblyscript - entities: - - Nft - - Account - - RoleAssignment - - RoleApproval - abis: - - name: SftRolesRegistry - file: ./abis/SftRolesRegistry.json - eventHandlers: - - event: RoleGranted(indexed uint256,indexed bytes32,address,uint256,uint256,address,address,uint64,bool,bytes) - handler: handleRoleGranted - - event: RoleRevoked(indexed uint256,indexed bytes32,address,uint256,uint256,address,address) - handler: handleRoleRevoked - - event: RoleApprovalForAll(indexed address,indexed address,bool) - handler: handleRoleApprovalForAll - - event: Withdrew(indexed uint256,indexed address,address,uint256,uint256) - handler: handleWithdrew - file: ./src/erc-7432/index.ts + file: ./src/erc-7432/index.ts \ No newline at end of file From 3c2b883532cacf119a069d18b7762e0c4af29e43 Mon Sep 17 00:00:00 2001 From: Daniel Lima Date: Tue, 12 Dec 2023 18:14:38 -0300 Subject: [PATCH 3/3] ON-553: refactor remove helpers --- schema.graphql | 10 ---- src/erc-721/transfer-handler.ts | 6 +-- src/erc-7432/role-revoked-handler.ts | 8 --- tests/erc-721/transfer-handler.test.ts | 45 +---------------- tests/erc-7432/grant-handler.test.ts | 67 ++------------------------ tests/erc-7432/revoke-handler.test.ts | 43 +++-------------- tests/helpers/assertion.ts | 2 - tests/mocks/entities.ts | 1 - utils/entities/helper-nft-ownership.ts | 32 ------------ utils/entities/index.ts | 1 - utils/entities/role-assignment.ts | 8 --- utils/entities/role.ts | 1 - 12 files changed, 14 insertions(+), 210 deletions(-) delete mode 100644 utils/entities/helper-nft-ownership.ts diff --git a/schema.graphql b/schema.graphql index ac418f1..4cbbcd6 100644 --- a/schema.graphql +++ b/schema.graphql @@ -39,7 +39,6 @@ type Role @entity { rolesRegistry: RolesRegistry! roleHash: Bytes! nft: Nft! - lastNonRevocableExpirationDate: BigInt! roleAssignments: [RoleAssignment!] @derivedFrom(field: "role") } @@ -56,13 +55,4 @@ type RolesRegistry @entity { id: ID! # contractAddress roles: [Role!] @derivedFrom(field: "rolesRegistry") roleApprovals: [RoleApproval!] @derivedFrom(field: "rolesRegistry") -} - -# Helper Entities - -type HelperNftOwnership @entity { - id: ID! # tokenAddress + tokenId + owner - nft: Nft! - originalOwner: Account! - isSameOwner: Boolean! } \ No newline at end of file diff --git a/src/erc-721/transfer-handler.ts b/src/erc-721/transfer-handler.ts index 9544f63..239e3fb 100644 --- a/src/erc-721/transfer-handler.ts +++ b/src/erc-721/transfer-handler.ts @@ -1,5 +1,5 @@ import { Transfer } from '../../generated/ERC721/ERC721' -import { upsertERC721Nft, upsertHelperNftOwnership } from '../../utils' +import { upsertERC721Nft } from '../../utils' import { log } from '@graphprotocol/graph-ts' /** @@ -15,9 +15,7 @@ export function handleTransfer(event: Transfer): void { const from = event.params.from.toHex() const to = event.params.to.toHex() - const nft = upsertERC721Nft(tokenAddress, tokenId, to) - upsertHelperNftOwnership(nft, from) - upsertHelperNftOwnership(nft, to) + upsertERC721Nft(tokenAddress, tokenId, to) log.warning('[erc-721][handleTransfer] NFT {} transferred from {} to {} tx {}', [ tokenId.toString(), diff --git a/src/erc-7432/role-revoked-handler.ts b/src/erc-7432/role-revoked-handler.ts index 20d4328..1017e09 100644 --- a/src/erc-7432/role-revoked-handler.ts +++ b/src/erc-7432/role-revoked-handler.ts @@ -71,14 +71,6 @@ export function handleRoleRevoked(event: RoleRevoked): void { roleAssignment.expirationDate = event.block.timestamp roleAssignment.save() - if (!roleAssignment.revocable) { - // smart contract validate that if a role is not revocable, it can only be revoked by the grantee - // in that case, we can set the lastNonRevocableExpirationDate to zero, assuming that the grantee is revoking its own role - const role = findOrCreateRole(rolesRegistry, nft, event.params._role) - role.lastNonRevocableExpirationDate = BigInt.zero() - role.save() - } - log.warning('[[erc-7432]handleRoleRevoked] Revoked RoleAssignment: {} NFT: {} tx: {}', [ roleAssignmentId, nftId, diff --git a/tests/erc-721/transfer-handler.test.ts b/tests/erc-721/transfer-handler.test.ts index 65e56bc..7cb8ee6 100644 --- a/tests/erc-721/transfer-handler.test.ts +++ b/tests/erc-721/transfer-handler.test.ts @@ -1,19 +1,11 @@ -import { assert, describe, test, clearStore, afterAll, beforeEach, afterEach } from 'matchstick-as' +import { assert, describe, test, clearStore, afterAll } from 'matchstick-as' import { BigInt } from '@graphprotocol/graph-ts' import { handleTransfer } from '../../src/erc-721' -import { NftType, generateERC721NftId, generateHelperNftOwnershipId, upsertHelperNftOwnership } from '../../utils' +import { generateERC721NftId } from '../../utils' import { createTransferEvent } from '../mocks/events' import { Addresses, ZERO_ADDRESS } from '../helpers/contants' -import { MockAddresses } from '../mocks/values' -import { Nft } from '../../generated/schema' -const originalOwnerAddress = MockAddresses[0] -const newOwnerAddress = MockAddresses[1] -const tokenAddress = MockAddresses[2] const tokenId = BigInt.fromI32(1) -const nftId = generateERC721NftId(tokenAddress, tokenId) - -let nft: Nft describe('ERC-721 Transfer Handler', () => { afterAll(() => { @@ -68,36 +60,3 @@ describe('ERC-721 Transfer Handler', () => { assert.fieldEquals('Nft', _id, 'owner', Addresses[2]) }) }) - -describe('HelperNftOwnership', () => { - beforeEach(() => { - nft = new Nft(nftId) - nft.tokenAddress = tokenAddress - nft.tokenId = tokenId - nft.owner = originalOwnerAddress - nft.type = NftType.ERC721 - nft.save() - }) - - test('Should change "isSameOwner" to false when NFT is transferred', () => { - const helperNftOwnership = upsertHelperNftOwnership(nft, nft.owner) - assert.entityCount('HelperNftOwnership', 1) - assert.fieldEquals('HelperNftOwnership', helperNftOwnership.id, 'isSameOwner', 'true') - - const event = createTransferEvent(originalOwnerAddress, newOwnerAddress, tokenId.toString(), tokenAddress) - handleTransfer(event) - - assert.entityCount('HelperNftOwnership', 2) - assert.fieldEquals('HelperNftOwnership', helperNftOwnership.id, 'isSameOwner', 'false') - assert.fieldEquals( - 'HelperNftOwnership', - generateHelperNftOwnershipId(tokenAddress, tokenId, newOwnerAddress), - 'isSameOwner', - 'true', - ) - }) - - afterEach(() => { - clearStore() - }) -}) diff --git a/tests/erc-7432/grant-handler.test.ts b/tests/erc-7432/grant-handler.test.ts index 601ee7d..41c071c 100644 --- a/tests/erc-7432/grant-handler.test.ts +++ b/tests/erc-7432/grant-handler.test.ts @@ -143,7 +143,6 @@ describe('ERC-7432 RoleGranted Handler', () => { expirationDate, data, event1.address.toHex(), - BigInt.zero(), ) validateRole( grantorAccount, @@ -153,7 +152,6 @@ describe('ERC-7432 RoleGranted Handler', () => { expirationDate, data, event2.address.toHex(), - BigInt.zero(), ) validateRole( grantorAccount, @@ -163,7 +161,6 @@ describe('ERC-7432 RoleGranted Handler', () => { expirationDate, data, event3.address.toHex(), - BigInt.zero(), ) }) @@ -218,66 +215,8 @@ describe('ERC-7432 RoleGranted Handler', () => { assert.entityCount('Account', 3) const grantorAccount = new Account(grantor) - validateRole( - grantorAccount, - new Account(Addresses[0]), - nft1, - RoleAssignmentId, - expirationDate, - data, - rolesRegistry, - BigInt.zero(), - ) - validateRole( - grantorAccount, - new Account(Addresses[1]), - nft2, - RoleAssignmentId, - expirationDate, - data, - rolesRegistry, - BigInt.zero(), - ) - validateRole( - grantorAccount, - new Account(Addresses[2]), - nft3, - RoleAssignmentId, - expirationDate, - data, - rolesRegistry, - BigInt.zero(), - ) - }) - - test('should update lastNonRevocableExpirationDate when revocable is false', () => { - const nft = createMockNft(tokenAddress, tokenId, grantor) - assert.entityCount('RoleAssignment', 0) - assert.entityCount('Role', 0) - assert.entityCount('Account', 1) - - const event1 = createNewRoleGrantedEvent( - RoleAssignmentId, - tokenId, - tokenAddress, - Addresses[0], - grantor, - expirationDate, - false, - data, - ) - handleRoleGranted(event1) - - const grantorAccount = new Account(grantor) - validateRole( - grantorAccount, - new Account(Addresses[0]), - nft, - RoleAssignmentId, - expirationDate, - data, - event1.address.toHex(), - expirationDate, - ) + validateRole(grantorAccount, new Account(Addresses[0]), nft1, RoleAssignmentId, expirationDate, data, rolesRegistry) + validateRole(grantorAccount, new Account(Addresses[1]), nft2, RoleAssignmentId, expirationDate, data, rolesRegistry) + validateRole(grantorAccount, new Account(Addresses[2]), nft3, RoleAssignmentId, expirationDate, data, rolesRegistry) }) }) diff --git a/tests/erc-7432/revoke-handler.test.ts b/tests/erc-7432/revoke-handler.test.ts index fac323e..376fcce 100644 --- a/tests/erc-7432/revoke-handler.test.ts +++ b/tests/erc-7432/revoke-handler.test.ts @@ -4,7 +4,7 @@ import { handleRoleRevoked } from '../../src/erc-7432' import { Bytes, BigInt } from '@graphprotocol/graph-ts' import { createMockAccount, createMockNft, createMockRoleAssignment } from '../mocks/entities' import { Addresses, ONE, TWO, ZERO_ADDRESS } from '../helpers/contants' -import { findOrCreateRole, findOrCreateRolesRegistry, generateERC721NftId, generateRoleAssignmentId } from '../../utils' +import { findOrCreateRolesRegistry, generateERC721NftId, generateRoleAssignmentId } from '../../utils' import { Account, Nft } from '../../generated/schema' import { validateRole } from '../helpers/assertion' @@ -106,9 +106,9 @@ describe('ERC-7432 RoleRevoked Handler', () => { assert.entityCount('RoleAssignment', 3) assert.entityCount('Role', 1) const revokerAccount = new Account(revoker) - validateRole(revokerAccount, account1, nft, RoleAssignmentId, ONE, data, rolesRegistry, BigInt.zero()) - validateRole(revokerAccount, account2, nft, RoleAssignmentId, ONE, data, rolesRegistry, BigInt.zero()) - validateRole(revokerAccount, account3, nft, RoleAssignmentId, ONE, data, rolesRegistry, BigInt.zero()) + validateRole(revokerAccount, account1, nft, RoleAssignmentId, ONE, data, rolesRegistry) + validateRole(revokerAccount, account2, nft, RoleAssignmentId, ONE, data, rolesRegistry) + validateRole(revokerAccount, account3, nft, RoleAssignmentId, ONE, data, rolesRegistry) }) test('should revoke multiple roles for different NFTs', () => { @@ -134,37 +134,8 @@ describe('ERC-7432 RoleRevoked Handler', () => { assert.entityCount('RoleAssignment', 3) assert.entityCount('Role', 3) const revokerAccount = new Account(revoker) - validateRole(revokerAccount, granteeAccount, nft1, RoleAssignmentId, ONE, data, rolesRegistry, BigInt.zero()) - validateRole(revokerAccount, granteeAccount, nft2, RoleAssignmentId, ONE, data, rolesRegistry, BigInt.zero()) - validateRole(revokerAccount, granteeAccount, nft3, RoleAssignmentId, ONE, data, rolesRegistry, BigInt.zero()) - }) - - test('should update lastNonRevocableExpirationDate when grantee is revoking a non revocable role', () => { - const nft = createMockNft(tokenAddress, tokenId, revoker) - const granteeAccount = createMockAccount(grantee) - const roleAssignment = createMockRoleAssignment( - RoleAssignmentId, - revoker, - grantee, - nft, - expirationDate, - rolesRegistry, - ) - roleAssignment.revocable = false - roleAssignment.save() - const role = findOrCreateRole(findOrCreateRolesRegistry(rolesRegistry), nft, RoleAssignmentId) - role.lastNonRevocableExpirationDate = expirationDate - role.save() - assert.entityCount('RoleAssignment', 1) - assert.entityCount('Role', 1) - assert.fieldEquals('Role', role.id, 'lastNonRevocableExpirationDate', expirationDate.toString()) - - const event = createNewRoleRevokedEvent(RoleAssignmentId, nft, revoker, grantee) - handleRoleRevoked(event) - - assert.entityCount('RoleAssignment', 1) - assert.entityCount('Role', 1) - const revokerAccount = new Account(revoker) - validateRole(revokerAccount, granteeAccount, nft, RoleAssignmentId, ONE, data, rolesRegistry, BigInt.zero()) + validateRole(revokerAccount, granteeAccount, nft1, RoleAssignmentId, ONE, data, rolesRegistry) + validateRole(revokerAccount, granteeAccount, nft2, RoleAssignmentId, ONE, data, rolesRegistry) + validateRole(revokerAccount, granteeAccount, nft3, RoleAssignmentId, ONE, data, rolesRegistry) }) }) diff --git a/tests/helpers/assertion.ts b/tests/helpers/assertion.ts index aa4ea30..a753789 100644 --- a/tests/helpers/assertion.ts +++ b/tests/helpers/assertion.ts @@ -16,13 +16,11 @@ export function validateRole( expirationDate: BigInt, data: Bytes, rolesRegistryAddress: string, - lastNonRevocableExpirationDate: BigInt, ): 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) - assert.fieldEquals('Role', roleId, 'lastNonRevocableExpirationDate', lastNonRevocableExpirationDate.toString()) const roleAssignmentId = generateRoleAssignmentId(rolesRegistry, grantor, grantee, nft, roleAssignment) assert.fieldEquals( diff --git a/tests/mocks/entities.ts b/tests/mocks/entities.ts index 17515ea..d91141f 100644 --- a/tests/mocks/entities.ts +++ b/tests/mocks/entities.ts @@ -42,7 +42,6 @@ export function createMockRoleAssignment( role.roleHash = roleHash role.nft = nft.id role.rolesRegistry = rolesRegistryAddress - role.lastNonRevocableExpirationDate = BigInt.zero() role.save() const roleAssignmentId = generateRoleAssignmentId( diff --git a/utils/entities/helper-nft-ownership.ts b/utils/entities/helper-nft-ownership.ts deleted file mode 100644 index 8bd9cd7..0000000 --- a/utils/entities/helper-nft-ownership.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { HelperNftOwnership, Nft } from '../../generated/schema' -import { BigInt } from '@graphprotocol/graph-ts' - -/** - @notice Generate HelperNftOwnership id. - @dev The id of the HelperNftOwnership is generated using the token address, the token id, and the owner address. - * @param tokenAddress The token address of the ERC721 NFT. - * @param tokenId The token id of the ERC721 NFT. - * @param owner The owner of the ERC721 NFT. - */ -export function generateHelperNftOwnershipId(tokenAddress: string, tokenId: BigInt, owner: string): string { - return tokenAddress + tokenId.toString() + owner -} - -/** - * @notice Insert or update an HelperNftOwnership. - * @dev It updates the "isSameOwner" as a way to check if the owner is the same. - * @param nft The nft of the given entity. - * @param owner The owner to be checked. - */ -export function upsertHelperNftOwnership(nft: Nft, owner: string): HelperNftOwnership { - const helperNftOwnershipId = generateHelperNftOwnershipId(nft.tokenAddress, nft.tokenId, owner) - let helperNftOwnership = HelperNftOwnership.load(helperNftOwnershipId) - if (!helperNftOwnership) { - helperNftOwnership = new HelperNftOwnership(helperNftOwnershipId) - helperNftOwnership.nft = nft.id - helperNftOwnership.originalOwner = nft.owner - } - helperNftOwnership.isSameOwner = nft.owner == owner - helperNftOwnership.save() - return helperNftOwnership -} diff --git a/utils/entities/index.ts b/utils/entities/index.ts index f8c58a9..2de9953 100644 --- a/utils/entities/index.ts +++ b/utils/entities/index.ts @@ -4,4 +4,3 @@ export * from './role-assignment' export * from './role-approval' export * from './roles-registry' export * from './role' -export * from './helper-nft-ownership' diff --git a/utils/entities/role-assignment.ts b/utils/entities/role-assignment.ts index bbb5caa..75b5c0e 100644 --- a/utils/entities/role-assignment.ts +++ b/utils/entities/role-assignment.ts @@ -53,14 +53,6 @@ export function upsertRoleAssignment(event: RoleGranted, grantor: Account, grant roleAssignment.data = event.params._data roleAssignment.updatedAt = event.block.timestamp - if (!roleAssignment.revocable) { - // if the role is not revocable, we update the lastNonRevocableExpirationDate - // since the grantor will not able to revoke the role before this date - // this is useful for the revocation check - role.lastNonRevocableExpirationDate = event.params._expirationDate - role.save() - } - roleAssignment.save() return roleAssignment } diff --git a/utils/entities/role.ts b/utils/entities/role.ts index c75df10..0601fb7 100644 --- a/utils/entities/role.ts +++ b/utils/entities/role.ts @@ -30,7 +30,6 @@ export function findOrCreateRole(rolesRegistry: RolesRegistry, nft: Nft, roleHas role.roleHash = roleHash role.nft = nft.id role.rolesRegistry = rolesRegistry.id - role.lastNonRevocableExpirationDate = BigInt.zero() role.save() }