Skip to content

Commit

Permalink
ERC-7432 Approvals (#4)
Browse files Browse the repository at this point in the history
tracking ERC-7432 role approvals
  • Loading branch information
ernanirst authored Oct 18, 2023
1 parent b76fae7 commit cb77cb7
Show file tree
Hide file tree
Showing 13 changed files with 179 additions and 9 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/master.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
- name: Build Polygon
run: npm run build:polygon
- name: Subgraph Deploy Polygon Satsuma
run: npx graph deploy orium-subgraph --version-label ${{ github.sha }} --node https://app.satsuma.xyz/api/subgraphs/deploy --ipfs https://ipfs.satsuma.xyz --deploy-key ${{ secrets.SATSUMA_DEPLOY_KEY }}
run: npx graph deploy polygon-roles-registry --version-label ${{ github.sha }} --node https://app.satsuma.xyz/api/subgraphs/deploy --ipfs https://ipfs.satsuma.xyz --deploy-key ${{ secrets.SATSUMA_DEPLOY_KEY }}
- name: Subgraph Promote Polygon Satsuma
run: |
curl -X POST https://app.satsuma.xyz/api/subgraphs/8c268d3e8b83112a7d0c732a9b88ba1c732da600bffaf68790171b9a0b5d5394/orium-subgraph/${{ github.sha }}/auto-promote-live -H "Content-Type: application/json" -H "x-api-key: ${{ secrets.SATSUMA_DEPLOY_KEY }}"
curl -X POST https://app.satsuma.xyz/api/subgraphs/8c268d3e8b83112a7d0c732a9b88ba1c732da600bffaf68790171b9a0b5d5394/polygon-roles-registry/${{ github.sha }}/auto-promote-live -H "Content-Type: application/json" -H "x-api-key: ${{ secrets.SATSUMA_DEPLOY_KEY }}"
8 changes: 8 additions & 0 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ type Nft @entity {
type Account @entity {
id: ID! # address
nfts: [Nft!] @derivedFrom(field: "owner")
roleApprovals: [RoleApproval!] @derivedFrom(field: "grantor")
}

type Role @entity {
Expand All @@ -21,3 +22,10 @@ type Role @entity {
revocable: Boolean!
data: Bytes!
}

type RoleApproval @entity {
id: ID! # grantorAddress + operatorAddress + tokenAddress
grantor: Account!
operator: Account!
tokenAddress: String!
}
1 change: 1 addition & 0 deletions src/erc7432/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { handleRoleGranted } from './role/grant-handler'
export { handleRoleRevoked } from './role/revoke-handler'
export { handleRoleApprovalForAll } from './role/role-approval-handler'
2 changes: 1 addition & 1 deletion src/erc7432/role/grant-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,5 @@ export function handleRoleGranted(event: RoleGranted): void {

const granteeAccount = findOrCreateAccount(event.params._grantee.toHex())
const role = findOrCreateRole(event, grantorAccount, granteeAccount, nft)
log.info('[handleRoleGranted] Role: {} NFT: {} Tx: {}', [role.id, nftId, event.transaction.hash.toHex()])
log.warning('[handleRoleGranted] Role: {} NFT: {} Tx: {}', [role.id, nftId, event.transaction.hash.toHex()])
}
2 changes: 1 addition & 1 deletion src/erc7432/role/revoke-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,5 @@ export function handleRoleRevoked(event: RoleRevoked): void {

role.expirationDate = event.block.timestamp
role.save()
log.info('[handleRoleRevoked] Revoked Role: {} NFT: {} Tx: {}', [roleId, nftId, event.transaction.hash.toHex()])
log.warning('[handleRoleRevoked] Revoked Role: {} NFT: {} Tx: {}', [roleId, nftId, event.transaction.hash.toHex()])
}
33 changes: 33 additions & 0 deletions src/erc7432/role/role-approval-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { RoleApprovalForAll } from '../../../generated/ERC7432-Immutable-Roles/ERC7432'
import {
findOrCreateAccount,
insertRoleApprovalIfNotExist,
deleteRoleApprovalIfExist,
generateRoleApprovalId,
} from '../../utils/helper'
import { log } from '@graphprotocol/graph-ts'

export function handleRoleApprovalForAll(event: RoleApprovalForAll): void {
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)

if (isApproved) {
const roleApproval = insertRoleApprovalIfNotExist(grantorAccount, operatorAccount, tokenAddress)
log.warning('[handleRoleApprovalForAll] Updated Role Approval: {} Tx: {}', [
roleApproval.id,
event.transaction.hash.toHex(),
])
} else {
const roleApprovalId = generateRoleApprovalId(grantorAccount, operatorAccount, tokenAddress)
deleteRoleApprovalIfExist(roleApprovalId)
log.warning('[handleRoleApprovalForAll] Removed Role Approval: {} Tx: {}', [
roleApprovalId,
event.transaction.hash.toHex(),
])
}
}
28 changes: 26 additions & 2 deletions src/utils/helper.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BigInt, Bytes } from '@graphprotocol/graph-ts'
import { Account, Nft, Role } from '../../generated/schema'
import { BigInt, Bytes, store } from '@graphprotocol/graph-ts'
import { Account, Nft, Role, RoleApproval } from '../../generated/schema'
import { RoleGranted } from '../../generated/ERC7432-Immutable-Roles/ERC7432'

export function findOrCreateAccount(id: string): Account {
Expand Down Expand Up @@ -45,3 +45,27 @@ export function findOrCreateRole(event: RoleGranted, grantor: Account, grantee:
role.save()
return role
}

export function generateRoleApprovalId(grantor: Account, operator: Account, tokenAddress: string): string {
return grantor.id + '-' + operator.id + '-' + tokenAddress.toLowerCase()
}

export function insertRoleApprovalIfNotExist(grantor: Account, operator: Account, tokenAddress: string): RoleApproval {
const roleApprovalId = generateRoleApprovalId(grantor, operator, tokenAddress)
let roleApproval = RoleApproval.load(roleApprovalId)
if (!roleApproval) {
roleApproval = new RoleApproval(roleApprovalId)
roleApproval.grantor = grantor.id
roleApproval.operator = operator.id
roleApproval.tokenAddress = tokenAddress.toLowerCase()
roleApproval.save()
}
return roleApproval
}

export function deleteRoleApprovalIfExist(roleApprovalId: string): void {
const roleApproval = RoleApproval.load(roleApprovalId)
if (roleApproval) {
store.remove('RoleApproval', roleApprovalId)
}
}
3 changes: 3 additions & 0 deletions subgraph-goerli.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ dataSources:
- Nft
- Account
- Role
- RoleApproval
abis:
- name: ERC7432
file: ./abis/ERC7432.json
Expand All @@ -47,4 +48,6 @@ dataSources:
handler: handleRoleGranted
- event: RoleRevoked(indexed bytes32,indexed address,indexed uint256,address,address)
handler: handleRoleRevoked
- event: RoleApprovalForAll(indexed address,indexed address,bool)
handler: handleRoleApprovalForAll
file: ./src/erc7432/index.ts
3 changes: 3 additions & 0 deletions subgraph-mumbai.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ dataSources:
- Nft
- Account
- Role
- RoleApproval
abis:
- name: ERC7432
file: ./abis/ERC7432.json
Expand All @@ -27,4 +28,6 @@ dataSources:
handler: handleRoleGranted
- event: RoleRevoked(indexed bytes32,indexed address,indexed uint256,address,address)
handler: handleRoleRevoked
- event: RoleApprovalForAll(indexed address,indexed address,bool)
handler: handleRoleApprovalForAll
file: ./src/erc7432/index.ts
3 changes: 3 additions & 0 deletions subgraph-polygon.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ dataSources:
- Nft
- Account
- Role
- RoleApproval
abis:
- name: ERC7432
file: ./abis/ERC7432.json
Expand All @@ -47,4 +48,6 @@ dataSources:
handler: handleRoleGranted
- event: RoleRevoked(indexed bytes32,indexed address,indexed uint256,address,address)
handler: handleRoleRevoked
- event: RoleApprovalForAll(indexed address,indexed address,bool)
handler: handleRoleApprovalForAll
file: ./src/erc7432/index.ts
59 changes: 59 additions & 0 deletions tests/erc7432/approval-handler.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { assert, describe, test, clearStore, afterEach } from 'matchstick-as'
import { createNewRoleApprovalForAllEvent } from '../helpers/events'
import { validateRoleApproval, createMockRoleApproval } from '../helpers/entities'
import { Addresses } from '../helpers/contants'
import { handleRoleApprovalForAll } from '../../src/erc7432'

const grantor = Addresses[0]
const operator = Addresses[1]
const tokenAddress = Addresses[2]

describe('ERC-7432 RoleApprovalForAll Handler', () => {
afterEach(() => {
clearStore()
})

describe('When RoleApproval exists', () => {
test('should remove approval when is set to false', () => {
createMockRoleApproval(grantor, operator, tokenAddress)
assert.entityCount('RoleApproval', 1)

const event = createNewRoleApprovalForAllEvent(grantor, operator, tokenAddress, false)
handleRoleApprovalForAll(event)

assert.entityCount('RoleApproval', 0)
})

test('should not do anything when is set to true', () => {
createMockRoleApproval(grantor, operator, tokenAddress)
assert.entityCount('RoleApproval', 1)

const event = createNewRoleApprovalForAllEvent(grantor, operator, tokenAddress, true)
handleRoleApprovalForAll(event)

assert.entityCount('RoleApproval', 1)
validateRoleApproval(grantor, operator, tokenAddress)
})
})

describe('When RoleApproval does not exist', () => {
test('should not do anything when approval is set to false', () => {
assert.entityCount('RoleApproval', 0)

const event = createNewRoleApprovalForAllEvent(grantor, operator, tokenAddress, false)
handleRoleApprovalForAll(event)

assert.entityCount('RoleApproval', 0)
})

test('should create approval when approval is set to true', () => {
assert.entityCount('RoleApproval', 0)

const event = createNewRoleApprovalForAllEvent(grantor, operator, tokenAddress, true)
handleRoleApprovalForAll(event)

assert.entityCount('RoleApproval', 1)
validateRoleApproval(grantor, operator, tokenAddress)
})
})
})
25 changes: 23 additions & 2 deletions tests/helpers/entities.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BigInt, Bytes } from '@graphprotocol/graph-ts'
import { Account, Nft, Role } from '../../generated/schema'
import { generateNftId, generateRoleId } from '../../src/utils/helper'
import { Account, Nft, Role, RoleApproval } from '../../generated/schema'
import { generateNftId, generateRoleId, generateRoleApprovalId } from '../../src/utils/helper'
import { assert } from 'matchstick-as'

export function createMockNft(tokenAddress: string, tokenId: string, ownerAddress: string): Nft {
Expand Down Expand Up @@ -35,6 +35,16 @@ export function createMockRole(role: Bytes, grantor: string, grantee: string, nf
return newRole
}

export function createMockRoleApproval(grantor: string, operator: string, tokenAddress: string): RoleApproval {
const roleApprovalId = generateRoleApprovalId(new Account(grantor), new Account(operator), tokenAddress)
const roleApproval = new RoleApproval(roleApprovalId)
roleApproval.grantor = grantor
roleApproval.operator = operator
roleApproval.tokenAddress = tokenAddress
roleApproval.save()
return roleApproval
}

export function validateRole(
grantor: Account,
grantee: Account,
Expand All @@ -51,3 +61,14 @@ export function validateRole(
assert.fieldEquals('Role', _id, 'expirationDate', expirationDate.toString())
assert.fieldEquals('Role', _id, 'data', data.toHex())
}

export function validateRoleApproval(grantor: string, operator: string, tokenAddress: string): void {
const roleApprovalId = generateRoleApprovalId(
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)
}
17 changes: 16 additions & 1 deletion tests/helpers/events.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { newMockEvent } from 'matchstick-as'
import { Transfer } from '../../generated/ERC721-Chronos-Traveler/ERC721'
import { Address, BigInt, Bytes, ethereum } from '@graphprotocol/graph-ts'
import { RoleGranted, RoleRevoked } from '../../generated/ERC7432-Immutable-Roles/ERC7432'
import { RoleGranted, RoleRevoked, RoleApprovalForAll } from '../../generated/ERC7432-Immutable-Roles/ERC7432'
import { Nft } from '../../generated/schema'

export function createTransferEvent(from: string, to: string, tokenId: string, address: string): Transfer {
Expand Down Expand Up @@ -48,6 +48,21 @@ export function createNewRoleGrantedEvent(
return event
}

export function createNewRoleApprovalForAllEvent(
grantor: string,
operator: string,
tokenAddress: string,
isApproved: boolean,
): RoleApprovalForAll {
const event = changetype<RoleApprovalForAll>(newMockEvent())
event.parameters = new Array<ethereum.EventParam>()
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 {
const ethAddress = ethereum.Value.fromBoolean(value)
return new ethereum.EventParam(name, ethAddress)
Expand Down

0 comments on commit cb77cb7

Please sign in to comment.