diff --git a/package.json b/package.json index ae60d1db4..6be81dea5 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,9 @@ "**/@graphprotocol/graph-cli", "**/@graphprotocol/graph-cli/**", "**/matchstick-as", - "**/matchstick-as/**" + "**/matchstick-as/**", + "**/@aragon/osx-commons-subgraph", + "**/@aragon/osx-commons-subgraph/**" ] } } diff --git a/packages/subgraph/CHANGELOG.md b/packages/subgraph/CHANGELOG.md index 009809625..916abf3e3 100644 --- a/packages/subgraph/CHANGELOG.md +++ b/packages/subgraph/CHANGELOG.md @@ -7,12 +7,49 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [UPCOMING] +## 1.4.1 + +## 1.5.0 + +### Changed + +- Fixed bug with negative number balances and missing delegation history for existing ERC20 tokens using `TokenVoting` +- Fixed bugs regarding inconsistent memberIds in various parts of the codebase. This primarily affects delegation. + +### Changed + +- Removed `createERC1155TokenCalls`, `createTokenCalls`, `createWrappedTokenCalls` and `createDummyActions` and use the equivalent functions `createERC20TokenCalls`, `createERC1155TokenCalls` and `createDummyAction` from `@aragon/sdk-commons-subgraph` +- Used `createDummyAction` from `@aragon/sdk-commons-subgraph` + +## 1.4.0 + ### Added +- Added `isSignaling` attribute to `TokenVotingProposal`, `AddresslistVotingProposal`, and `MultisigProposal` that is set to true for proposals having an empty action array. + ### Changed +- Renamed `potentiallyExecutable` attribute to `approvalReached` and stopped setting it to `true` during multisig proposal creation if `minApprovals = 1` when zero approvals were given yet. + +## 1.3.1 + +### Added + +- Add support for `Granted` & `Revoked` event in `PluginRepo` +- Add one-to-many relationship from `Dao` & `PluginRepo` to `Permission` + +### Changed + +- Renamed `fetchERC20` & `fetchWrappedERC20` to `fetchOrCreateERC20Entity` & `fetchOrCreateWrappedERC20Entity` respectively. +- Changed type of `token` attribute of `ERC20Transfer` from `ERC20Contract` to `Token`. +- Refactored `Permission` entity & added `pluginRepo` attribute. +- Fixed wrong token voting member deletion, when balance & voting power was zero, but it was still delegating to another address. + ### Removed +- Removed `SignatureValidatorSet` event. +- Removed `ContractPermissionId` entity. + ## [1.3.0] ### Added @@ -69,7 +106,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Changed `IPluginInstallation` to `IPlugin`. - Changed `release: Int!` to `release: PluginRelease!` in `PluginVersion` - Changed `versions` to `releases` in `PluginRepo`. -- Changes `Permission` entity to be muteable. +- Changes `Permission` entity to be mutable. ### Removed diff --git a/packages/subgraph/README.md b/packages/subgraph/README.md index e59c5fb20..0b4d58057 100644 --- a/packages/subgraph/README.md +++ b/packages/subgraph/README.md @@ -70,7 +70,8 @@ You have the option to deploy your subgraph locally, this is how you can do it. Make sure u set the env variable `` to 'localhost' and `` to 'aragon/aragon-core'. -To update the local manifest after the contracts have been compiled, +To update the local manifest after the contracts have been compiled, + ```console yarn updateLocalManifest ``` @@ -81,11 +82,13 @@ To start: yarn start:dev ``` -When there's changes in the contract's package, run +When there are changes in the contract's package, run + ```console yarn buildAndStart:dev -``` -to force build the hardhat docker image containing the contracts. +``` + +to force build the hardhat docker image containing the contracts. Ideally in the future, the docker image should be part of the contract's CI/CD flow. Each time a new version of contracts is released, a new docker image should also be released, and the version number should be changed on the docker-compose in the subgraph directory. @@ -111,3 +114,15 @@ yarn stop:dev | subgraph:patch | Applies a patch version bump for the changelog and package.json | | subgraph:minor | Applies a minor version bump for the changelog and package.json | | subgraph:major | Applies a major version bump for the changelog and package.json | + +## Contributing + +If you like what we're doing and would love to support, please review our `CONTRIBUTING_GUIDE.md` [here](https://github.com/aragon/osx/blob/develop/CONTRIBUTION_GUIDE.md). We'd love to build with you. + +## Security + +If you believe you've found a security issue, we encourage you to notify us. We welcome working with you to resolve the issue promptly. + +Security Contact Email: sirt@aragon.org + +Please do not use the issue tracker for security issues. diff --git a/packages/subgraph/docker/docker-compose.yml b/packages/subgraph/docker/docker-compose.yml index 06c9162d2..465ad23c0 100644 --- a/packages/subgraph/docker/docker-compose.yml +++ b/packages/subgraph/docker/docker-compose.yml @@ -1,11 +1,11 @@ version: '3' services: hardhat: - build: - context: ../../../ - dockerfile: packages/subgraph/docker/hardhat.Dockerfile - ports: - - 8545:8545 + build: + context: ../../../ + dockerfile: packages/subgraph/docker/hardhat.Dockerfile + ports: + - 8545:8545 graph-node: image: graphprotocol/graph-node ports: @@ -38,11 +38,7 @@ services: image: postgres ports: - '5432:5432' - command: - [ - "postgres", - "-cshared_preload_libraries=pg_stat_statements" - ] + command: ['postgres', '-cshared_preload_libraries=pg_stat_statements'] environment: POSTGRES_USER: graph-node POSTGRES_PASSWORD: let-me-in @@ -50,7 +46,7 @@ services: # FIXME: remove this env. var. which we shouldn't need. Introduced by # , maybe as a # workaround for https://github.com/docker/for-mac/issues/6270? - PGDATA: "/var/lib/postgresql/data" - POSTGRES_INITDB_ARGS: "-E UTF8 --locale=C" + PGDATA: '/var/lib/postgresql/data' + POSTGRES_INITDB_ARGS: '-E UTF8 --locale=C' volumes: - ./data/postgres:/var/lib/postgresql/data diff --git a/packages/subgraph/manifest/data/arbitrumSepolia.json b/packages/subgraph/manifest/data/arbitrumSepolia.json index 0f509f249..63c15f838 100644 --- a/packages/subgraph/manifest/data/arbitrumSepolia.json +++ b/packages/subgraph/manifest/data/arbitrumSepolia.json @@ -1,24 +1,23 @@ { - "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", - "network": "arbitrum-sepolia", - "dataSources": { - "DAORegistry": { - "name": "DAORegistry", - "address": "0x308a1DC5020c4B5d992F5543a7236c465997fecB", - "startBlock": 2827166 - }, - "PluginRepoRegistry": { - "name": "PluginRepoRegistry", - "address": "0x35B62715459cB60bf6dC17fF8cfe138EA305E7Ee", - "startBlock": 2827170 - }, - "PluginSetupProcessors": [ - { - "name": "PluginSetupProcessor", - "address": "0xC24188a73dc09aA7C721f96Ad8857B469C01dC9f", - "startBlock": 2827173 - } - ] - } + "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", + "network": "arbitrum-sepolia", + "dataSources": { + "DAORegistry": { + "name": "DAORegistry", + "address": "0x308a1DC5020c4B5d992F5543a7236c465997fecB", + "startBlock": 2827166 + }, + "PluginRepoRegistry": { + "name": "PluginRepoRegistry", + "address": "0x35B62715459cB60bf6dC17fF8cfe138EA305E7Ee", + "startBlock": 2827170 + }, + "PluginSetupProcessors": [ + { + "name": "PluginSetupProcessor", + "address": "0xC24188a73dc09aA7C721f96Ad8857B469C01dC9f", + "startBlock": 2827173 + } + ] } - \ No newline at end of file +} diff --git a/packages/subgraph/manifest/subgraph.placeholder.yaml b/packages/subgraph/manifest/subgraph.placeholder.yaml index 4834b91b3..6268cd121 100644 --- a/packages/subgraph/manifest/subgraph.placeholder.yaml +++ b/packages/subgraph/manifest/subgraph.placeholder.yaml @@ -123,6 +123,8 @@ templates: file: $ARAGON_OSX_MODULE/../subgraph/abis/DAO_v1_0_0.json - name: CallbackHandler file: $ARAGON_OSX_MODULE/artifacts/src/core/utils/CallbackHandler.sol/CallbackHandler.json + - name: GovernanceWrappedERC20 + file: $ARAGON_OSX_MODULE/artifacts/src/token/ERC20/governance/GovernanceWrappedERC20.sol/GovernanceWrappedERC20.json eventHandlers: - event: MetadataSet(bytes) handler: handleMetadataSet @@ -142,8 +144,6 @@ templates: handler: handleTrustedForwarderSet - event: StandardCallbackRegistered(bytes4,bytes4,bytes4) handler: handleStandardCallbackRegistered - - event: SignatureValidatorSet(address) - handler: handleSignatureValidatorSet - event: NewURI(string) handler: handleNewURI - name: DaoTemplateV1_3_0 @@ -175,6 +175,8 @@ templates: file: $ARAGON_OSX_MODULE/artifacts/src/plugins/governance/majority-voting/addresslist/AddresslistVoting.sol/AddresslistVoting.json - name: CallbackHandler file: $ARAGON_OSX_MODULE/artifacts/src/core/utils/CallbackHandler.sol/CallbackHandler.json + - name: GovernanceWrappedERC20 + file: $ARAGON_OSX_MODULE/artifacts/src/token/ERC20/governance/GovernanceWrappedERC20.sol/GovernanceWrappedERC20.json eventHandlers: - event: Executed(indexed address,bytes32,(address,uint256,bytes)[],uint256,uint256,bytes[]) handler: handleExecuted @@ -357,3 +359,8 @@ templates: handler: handleVersionCreated - event: ReleaseMetadataUpdated(uint8,bytes) handler: handleReleaseMetadataUpdated + - event: Granted(indexed bytes32,indexed address,address,indexed address,address) + handler: handleGranted + - event: Revoked(indexed bytes32,indexed address,address,indexed address) + handler: handleGranted + diff --git a/packages/subgraph/package.json b/packages/subgraph/package.json index 391ce6344..2d0bbb313 100644 --- a/packages/subgraph/package.json +++ b/packages/subgraph/package.json @@ -1,6 +1,6 @@ { "name": "@aragon/osx-subgraph", - "version": "1.3.0", + "version": "1.4.1", "description": "The Aragon OSx Subgraph", "homepage": "https://github.com/aragon/osx", "license": "AGPL-3.0-or-later", @@ -19,9 +19,7 @@ "buildAndStart:dev": "docker-compose -f docker/docker-compose.yml up -d --build hardhat && sleep 30 && yarn create:local && yarn deploy:local", "test:fast": "graph test", "test": "graph test -r", - "coverage": "graph test -c", - "formatting:check": "prettier '**/*.{json,ts,js}' -c", - "formatting:write": "prettier '**/*.{json,ts,js}' --write" + "coverage": "graph test -c" }, "devDependencies": { "@graphprotocol/graph-cli": "0.52.0", @@ -29,9 +27,12 @@ "@typescript-eslint/eslint-plugin": "^5.18.0", "@typescript-eslint/parser": "^5.18.0", "eslint": "^8.12.0", - "matchstick-as": "^0.5.1", + "matchstick-as": "^0.5.2", "mustache": "^4.2.0", "ts-morph": "^17.0.1", "typescript": "^4.9.5" + }, + "dependencies": { + "@aragon/osx-commons-subgraph": "^0.0.4" } } diff --git a/packages/subgraph/schema.graphql b/packages/subgraph/schema.graphql index 1ec308f85..5594dc5c9 100644 --- a/packages/subgraph/schema.graphql +++ b/packages/subgraph/schema.graphql @@ -1,203 +1,458 @@ # Types -# Token +############################# +########## Tokens ########### +############################# +""" +Interface representing a generic token. Each entity implementing this interface can be queried as Token. +""" interface Token { - id: ID! # use address as id + " The address of the Token Smart Contract. " + id: ID! + + " The name of the token, mirrored from the smart contract. " name: String + + " The symbol of the token, mirrored from the smart contract. " symbol: String } +""" +An [ERC20 Token Smart Contract](https://eips.ethereum.org/EIPS/eip-20), of type Token. +""" type ERC20Contract implements Token @entity(immutable: true) { - id: ID! # use address as id + " The address of the [ERC20 Token Smart Contract](https://eips.ethereum.org/EIPS/eip-20). " + id: ID! + + " The name of the token, mirrored from the smart contract. " name: String + + " The symbol of the token, mirrored from the smart contract. " symbol: String + + " The number of decimals of the token, mirrored from the smart contract. " decimals: Int! } -type ERC20WrapperContract implements Token @entity(inmutable: true) { - id: ID! # use address as id +""" +Wrapped [ERC20 Token Smart Contract](https://eips.ethereum.org/EIPS/eip-20). Used for token voting. +""" +type ERC20WrapperContract implements Token @entity(immutable: true) { + " The address of the [ERC20 Token Smart Contract](https://eips.ethereum.org/EIPS/eip-20). " + id: ID! + + " The name of the wrapped token, mirrored from the smart contract. " name: String + + " The symbol of the wrapped token, mirrored from the smart contract. " symbol: String + + " The number of decimals of the wrapped token, mirrored from the smart contract. " decimals: Int! + + " The underlying [ERC20 Token Smart Contract](https://eips.ethereum.org/EIPS/eip-20) this wrapper represents. " underlyingToken: ERC20Contract! } +""" +[ERC721 Token Standard](https://eips.ethereum.org/EIPS/eip-721), Non-Fungible Token (NFT), of type Token. +""" type ERC721Contract implements Token @entity(immutable: true) { - id: ID! # use address as id + " The address of the [ERC721 Token Smart Contract](https://eips.ethereum.org/EIPS/eip-721). " + id: ID! + + " The name of the token, mirrored from the smart contract. " name: String + + " The symbol of the token, mirrored from the smart contract. " symbol: String } +""" +[ERC1155 Multi-Token Standard](https://eips.ethereum.org/EIPS/eip-1155), fungible, non-fungible, and semi-fungible tokens all in one contract, +type of Token. +""" type ERC1155Contract implements Token @entity(immutable: true) { - id: ID! # use address as id + " The address of the [ERC1155 Token Smart Contract](https://eips.ethereum.org/EIPS/eip-1155). " + id: ID! + + " The name of the [ERC1155 Token Smart Contract](https://eips.ethereum.org/EIPS/eip-1155). " name: String + + " The symbol of the [ERC1155 Token Smart Contract](https://eips.ethereum.org/EIPS/eip-1155). " symbol: String } -# Token Balances +########################### +##### Token Balances ###### +########################### + +""" +Balance of the DAO for any kind of token. Each entity implementing this interface can be queried as TokenBalance. +""" interface TokenBalance { - id: ID! # dao address + token address + " The concatenation of DAO Smart contract address and Token Smart Contract address. Joined by '_'. " + id: ID! + + " The DAO that holds the Tokens" dao: Dao! + + " The block timestamp of the last update (transfer) for this token balance. " lastUpdated: BigInt! } +""" +The ERC20 Token balances for the DAO. +""" type ERC20Balance implements TokenBalance @entity { + " The concatenation of DAO Smart contract address and Token Smart contract address. Joined by '_'. " id: ID! + + " The [ERC20 Token Smart Contract](https://eips.ethereum.org/EIPS/eip-20) instance. " token: ERC20Contract! + + " The DAO that holds the ERC20 token. " dao: Dao! + + " The amount of ERC20 tokens held by the DAO. " balance: BigInt! + + " The block timestamp of the last update (transfer) for this token balance. " lastUpdated: BigInt! } +""" +The Native token balances of the DAO, representing the amount of the blockchain's native currency +(e.g., Ether on Ethereum, MATIC on Polygon) held by the DAO. +""" type NativeBalance implements TokenBalance @entity { + " The concatenation of DAO Smart contract address and Token Smart contract address. Joined by '_'. " id: ID! + + " The DAO that holds the native tokens" dao: Dao! + + " The amount of native tokens held by the DAO. " balance: BigInt! + + " The block timestamp of the last update (transfer) for this native token balance. " lastUpdated: BigInt! } +""" +The NFT balances of the DAO. Including all Token IDs held by the DAO per [ERC721 Token Smart Contract](https://eips.ethereum.org/EIPS/eip-721). +""" type ERC721Balance implements TokenBalance @entity { + " The concatenation of DAO Smart Contract address and [ERC721 Token Smart Contract](https://eips.ethereum.org/EIPS/eip-721) address. Joined by '_'. " id: ID! + + " The [ERC721 Token Smart Contract](https://eips.ethereum.org/EIPS/eip-721) instance. " token: ERC721Contract! + + " The DAO that holds tokens from a [ERC721 Token Smart Contract](https://eips.ethereum.org/EIPS/eip-721). " dao: Dao! + + " The NFTs from one ERC721 Contract held by the DAO. " tokenIds: [BigInt!]! + + " The block timestamp of the last update (transfer) for this token balance. " lastUpdated: BigInt! } +""" +The ERC1155 Token balances of the DAO. Including all Token IDs and it's amounts held by the DAO per [ERC1155 Token Smart Contract](https://eips.ethereum.org/EIPS/eip-1155). +""" type ERC1155Balance implements TokenBalance @entity { + " The concatenation of DAO Smart Contract address and [ERC1155 Token Smart Contract](https://eips.ethereum.org/EIPS/eip-1155) address. Joined by '_'. " id: ID! + + " The [ERC1155 Token Smart Contract](https://eips.ethereum.org/EIPS/eip-1155) instance. " token: ERC1155Contract! + + " The DAO that holds tokens from a [ERC1155 Token Smart Contract](https://eips.ethereum.org/EIPS/eip-1155). " dao: Dao! + + " Balance for each token ID from an [ERC1155 Token Smart Contract](https://eips.ethereum.org/EIPS/eip-1155). " balances: [ERC1155TokenIdBalance!]! @derivedFrom(field: "balance") + + " The URI of the [ERC1155 Token Smart Contract](https://eips.ethereum.org/EIPS/eip-1155). " metadataUri: String! + + " The block timestamp of the last update (transfer) for this token balance. " lastUpdated: BigInt! } +""" +The DAO token balance for each token within an [ERC1155 Token Smart Contract](https://eips.ethereum.org/EIPS/eip-1155) +""" type ERC1155TokenIdBalance @entity { + " The concatenation of DAO Smart Contract address, [ERC1155 Token Smart Contract](https://eips.ethereum.org/EIPS/eip-1155) and token ID. Joined by '_'. " id: ID! + + " One to Many relationship linking with ERC1155Balance entity. " balance: ERC1155Balance! + + " The ID for each different token in an [ERC1155 Token Smart Contract](https://eips.ethereum.org/EIPS/eip-1155) held by the DAO. " tokenId: BigInt! + + " The number of tokens held by the DAO per [ERC1155 Token Smart Contract](https://eips.ethereum.org/EIPS/eip-1155) and token ID. " amount: BigInt! + + " The block timestamp of the last update (transfer) for this token balance. " lastUpdated: BigInt! } -# Token Transfers +########################### +##### Token Transfers ##### +########################### +""" +Classifies the types of transfers from the DAO's point of view. +""" enum TransferType { - ExternalTransfer # when from/to are both NOT equal to dao. + """ + When a DAO facilitates a transfer and neither the source nor the destination addresses match the DAO's. The DAO must + have token approval to execute the transfer via proposal, acting as an intermediary in the process. + """ + ExternalTransfer + + " A withdrawal form the DAO treasury. Executed by the DAO via proposal calling `transfer`. " Withdraw + + """ + A deposit to the DAO treasury. Using either the DAO `deposit` function or executed by the DAO via proposal calling `transferFrom` as an action to transfer to itself. + The DAO must have token approval. + """ Deposit } +""" +Interface representing a generic token transfer associated with the DAO. Each entity implementing this interface can be queried as TokenTransfer (refer to `Enum` `TransferType` for a better understanding of transfers associated with the DAO meaning). +""" interface TokenTransfer { + " The concatenation of the transfer transaction hash, the log index and the action index. Joined by '_'. " id: ID! + + " The DAO that manages the transfer. " dao: Dao! + + " The Ethereum address from which the tokens are being transferred. " from: Bytes! + + " The Ethereum address receiving the transfer. " to: Bytes! + + " The type of transfer (External, Withdraw, Deposit) from the DAO's POV. " type: TransferType! + + " The associated DAO proposal, if applicable. Using the DAO `deposit` function there's no need for proposal. " proposal: IProposal + + " The hash of the transfer to track the transaction. " txHash: Bytes! + + " The timestamp of the block including transfer. " createdAt: BigInt! } +""" +Transfers of ERC20 token associated with the DAO, including transfers sent to, received by, or intermediated by the DAO (refer to `Enum` `TransferType` for a better understanding of transfers associated with the DAO meaning). +""" type ERC20Transfer implements TokenTransfer @entity(immutable: true) { + " The concatenation of the transfer transaction hash, the log index and the action index. Joined by '_'. " id: ID! + + " The DAO that manages the transfer. " dao: Dao! - token: ERC20Contract! + + " The ERC20 Smart Contract Token entity involved in the transfer. " + token: Token! + + " The amount of ERC20 token transferred. " amount: BigInt! + + " The Ethereum address from which the ERC20 tokens are being transferred. " from: Bytes! + + " The Ethereum address receiving the ERC20 tokens. " to: Bytes! + + " The associated DAO proposal, if applicable. Using the DAO `deposit` function there's no need for proposal. " proposal: IProposal + + " The type of transfer (External, Withdraw, Deposit) from the DAO's POV. " type: TransferType! + + " The hash of the ERC20 token transfer to track the transaction. " txHash: Bytes! + + " The timestamp of the block including the ERC20 token transfer. " createdAt: BigInt! } +""" +Transfers of ERC721 token associated with the DAO, including transfers sent to, received by, or intermediated by the DAO (refer to `Enum` `TransferType` for a better understanding of transfers associated with the DAO meaning). +""" type ERC721Transfer implements TokenTransfer @entity(immutable: true) { + " The concatenation of the transfer transaction hash, the log index and the action index. Joined by '_'. " id: ID! + + " The DAO that manages the transfer. " dao: Dao! + + " The ERC721 Smart Contract Token entity involved in the transfer. " token: ERC721Contract! + + " The ID of the transferred token. " tokenId: BigInt! + + " The Ethereum address from which the ERC721 token is being transferred. " from: Bytes! + + " The Ethereum address receiving the ERC721 token. " to: Bytes! + + " The associated DAO proposal, if applicable. Using the DAO `deposit` function there's no need for proposal. " proposal: IProposal + + " The type of transfer (External, Withdraw, Deposit) from the DAO's POV. " type: TransferType! + + " The hash of the ERC721 token transfer to track the transaction. " txHash: Bytes! + + " The timestamp of the block including the ERC721 token transfer . " createdAt: BigInt! } +""" +Transfers of ERC1155 token associated with the DAO, including transfers sent to, received by, or intermediated by the DAO (refer to `Enum` `TransferType` for a better understanding of transfers associated with the DAO meaning). +""" type ERC1155Transfer implements TokenTransfer @entity(immutable: true) { + " The concatenation of the transfer transaction hash, the log index and the action index. Joined by '_'. " id: ID! + + " The DAO that manages the transfer. " dao: Dao! + + " The ERC1155 Smart Contract Token entity involved in the transfer. " token: ERC1155Contract! + + " The ID of the transferred token. " tokenId: BigInt! + + " The amount of transferred tokens. " amount: BigInt! + + " The Ethereum address performing the token transfer. " operator: Bytes! + + " The Ethereum address from which the tokens are being transferred. " from: Bytes! + + " The Ethereum address receiving the tokens. " to: Bytes! + + " The associated DAO proposal, if applicable. Using the DAO `deposit` function there's no need for proposal. " proposal: IProposal + + " The type of transfer (External, Withdraw, Deposit) from the DAO's POV. " type: TransferType! + + " The hash of the ERC1155 token transfer to track the transaction. " txHash: Bytes! + + " The timestamp of the block including the ERC1155 token transfer. " createdAt: BigInt! } +""" +Transfers of Native token associated with the DAO, including transfers sent to, received by, or intermediated by the DAO (refer to `Enum` `TransferType` for a better understanding of transfers associated with the DAO meaning). +""" type NativeTransfer implements TokenTransfer @entity(immutable: true) { + " The concatenation of the transfer transaction hash, the log index and the action index (set to 0). Joined by '_'. " id: ID! + + " The DAO that manages the transfer. " dao: Dao! + + " The amount of transferred native tokens . " amount: BigInt! + + " The Ethereum address from which the native tokens are being transferred. " from: Bytes! + + " The Ethereum address receiving the native tokens. " to: Bytes! + + " The reference describing the deposit reason. " reference: String! + + " The associated DAO proposal, if applicable. Using the DAO `deposit` function there's no need for proposal. " proposal: IProposal - type: TransferType! - txHash: Bytes! - createdAt: BigInt! -} -# ACL + " The type of transfer (External, Withdraw, Deposit) from the DAO's POV. " + type: TransferType! -type ContractPermissionId @entity(immutable: true) { - id: ID! # where + permissionId - dao: Dao! - where: Bytes! - permissionId: Bytes! -} + " The hash of the native token transfer to track the transaction. " + txHash: Bytes! -# Cannot be immutable because a permission can be revoked and granted in the same TX. -# This results in an error in the subgraph if the entity is immutable. -type Permission @entity { - "no need to store granted as we can delete permission when revoked" - id: ID! # where + permissionId + who - dao: Dao! - where: Bytes! - contractPermissionId: ContractPermissionId! - who: Bytes! - actor: Bytes! - condition: Bytes + " The timestamp of the native token transfer block. " + createdAt: BigInt! } -# Executions +############################# +######## Executions ######### +############################# +""" +Actions are represented by solidity struct and executed by the DAO's `execute` function, resulting in an external call. +""" type Action @entity { + " Concatenation of the proposalID and the index of the action. Joined by '_'. " id: ID! + + " The address to call. " to: Bytes! + + " The native token value to be sent with the call. " value: BigInt! + + " The bytes-encoded function selector and calldata for the call. " data: Bytes! + + " The DAO that will `execute` the function. " dao: Dao! + + " The proposal of the DAO containing the action to be executed. " proposal: IProposal! + + " The result obtained from the executed action in `bytes`. " execResult: Bytes } +""" +The supported callback functions for ERC standards registered with [ERC-165](https://eips.ethereum.org/EIPS/eip-165) interface ID and callback function signature. +""" type StandardCallback @entity { + " Concatenation of the DAO address and the interfaceID. Joined by '_'. " id: ID! + + " The DAO associated with the supported callback functions. " dao: Dao! + + " The interface identifier, as specified in [ERC-165](https://eips.ethereum.org/EIPS/eip-165) (XOR of all function selectors in the interface). " interfaceId: Bytes! + + " The selector of the registered callback function. " callbackSelector: Bytes! + + " The magic number registered for the function signature. " magicNumber: Bytes! } -# Dao +# DAO type Dao @entity { id: ID! # use address as id @@ -210,7 +465,6 @@ type Dao @entity { actions: [Action!]! @derivedFrom(field: "dao") transfers: [TokenTransfer!]! @derivedFrom(field: "dao") balances: [TokenBalance!]! @derivedFrom(field: "dao") - contractPermissionIds: [ContractPermissionId!]! @derivedFrom(field: "dao") permissions: [Permission!]! @derivedFrom(field: "dao") proposals: [IProposal!]! @derivedFrom(field: "dao") trustedForwarder: Bytes @@ -220,6 +474,25 @@ type Dao @entity { plugins: [PluginInstallation!]! @derivedFrom(field: "dao") } +# ACL + +# Cannot be immutable because a permission can be revoked and granted in the same TX. +# This results in an error in the subgraph if the entity is immutable. +# No need to store granted as we can delete permission when revoked +type Permission @entity { + id: ID! # emitting contract Address (DAO or PluginRepo) + permissionId + where + who + where: Bytes! + permissionId: Bytes! + who: Bytes! + actor: Bytes! + condition: Bytes + + # The following attributes is used to create + # one-to-many between DAO or PluginRepo to Permission + dao: Dao + pluginRepo: PluginRepo +} + # Plugins type PluginRepo @entity(immutable: true) { @@ -230,6 +503,8 @@ type PluginRepo @entity(immutable: true) { preparations: [PluginPreparation!]! @derivedFrom(field: "pluginRepo") # Holds all installed and uninstalled installations. installations: [PluginInstallation!] @derivedFrom(field: "appliedPluginRepo") + + permissions: [Permission!]! @derivedFrom(field: "pluginRepo") } type PluginSetup @entity(immutable: true) { @@ -307,6 +582,7 @@ type PluginInstallation @entity { enum PluginPreparationType { Installation Update + Uninstallation } enum PluginPreparationState { @@ -407,7 +683,7 @@ type TokenVotingMember @entity { plugin: TokenVotingPlugin! # delegates - delegatee: TokenVotingMember! + delegatee: TokenVotingMember votingPower: BigInt # we assume token owners and/or delegatees are members delegators: [TokenVotingMember!]! @derivedFrom(field: "delegatee") @@ -462,7 +738,8 @@ type TokenVotingProposal implements IProposal @entity { startDate: BigInt! endDate: BigInt! creationBlockNumber: BigInt! - potentiallyExecutable: Boolean! + approvalReached: Boolean! + isSignaling: Boolean! earlyExecutable: Boolean executionDate: BigInt executionBlockNumber: BigInt @@ -537,7 +814,8 @@ type AddresslistVotingProposal implements IProposal @entity { startDate: BigInt! endDate: BigInt! creationBlockNumber: BigInt! - potentiallyExecutable: Boolean! + approvalReached: Boolean! + isSignaling: Boolean! earlyExecutable: Boolean executionDate: BigInt executionBlockNumber: BigInt @@ -641,7 +919,8 @@ type MultisigProposal implements IProposal @entity { snapshotBlock: BigInt! minApprovals: Int! approvals: Int - potentiallyExecutable: Boolean! + approvalReached: Boolean! + isSignaling: Boolean! executed: Boolean! executionDate: BigInt executionBlockNumber: BigInt diff --git a/packages/subgraph/scripts/updateLocalManifest.js b/packages/subgraph/scripts/updateLocalManifest.js index 85943fe2c..40e721283 100755 --- a/packages/subgraph/scripts/updateLocalManifest.js +++ b/packages/subgraph/scripts/updateLocalManifest.js @@ -5,7 +5,7 @@ const path = require('path'); async function main() { const networks = await fs.readdir('../contracts/deployments', { - withFileTypes: true + withFileTypes: true, }); for (const network of networks) { if (network.isDirectory()) { @@ -14,10 +14,9 @@ async function main() { const contracts = await fs.readdir(networkPath, {withFileTypes: true}); const manifest = { - info: - '# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest', + info: '# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest', network: networkName, - dataSources: {} + dataSources: {}, }; for (const contract of contracts) { @@ -47,7 +46,7 @@ async function main() { manifest.dataSources[dataSourceName] = { name, address: contractJson.address, - startBlock: contractJson.receipt.blockNumber + startBlock: contractJson.receipt.blockNumber, }; } } diff --git a/packages/subgraph/src/dao/dao_v1_0_0.ts b/packages/subgraph/src/dao/dao_v1_0_0.ts index b651b7fe0..09ccd6a36 100644 --- a/packages/subgraph/src/dao/dao_v1_0_0.ts +++ b/packages/subgraph/src/dao/dao_v1_0_0.ts @@ -1,5 +1,9 @@ -import {BigInt, Bytes, store} from '@graphprotocol/graph-ts'; - +import { + Dao, + Permission, + StandardCallback, + TransactionActionsProposal, +} from '../../generated/schema'; import { MetadataSet, Executed, @@ -8,37 +12,35 @@ import { Granted, Revoked, TrustedForwarderSet, - SignatureValidatorSet, StandardCallbackRegistered, CallbackReceived, - NewURI + NewURI, } from '../../generated/templates/DaoTemplateV1_0_0/DAO'; -import { - Dao, - ContractPermissionId, - Permission, - StandardCallback, - TransactionActionsProposal -} from '../../generated/schema'; - import {ADDRESS_ZERO} from '../utils/constants'; - -import {handleERC721Received} from '../utils/tokens/erc721'; -import {handleERC20Deposit} from '../utils/tokens/erc20'; -import {handleNativeDeposit} from '../utils/tokens/eth'; import { onERC1155BatchReceived, onERC1155Received, - onERC721Received + onERC721Received, } from '../utils/tokens/common'; -import {handleAction, updateProposalWithFailureMap} from './utils'; +import {handleERC20Deposit} from '../utils/tokens/erc20'; +import {handleERC721Received} from '../utils/tokens/erc721'; import { handleERC1155BatchReceived, - handleERC1155Received + handleERC1155Received, } from '../utils/tokens/erc1155'; +import {handleNativeDeposit} from '../utils/tokens/eth'; +import {handleAction, updateProposalWithFailureMap} from './utils'; +import { + generateDaoEntityId, + generatePermissionEntityId, + generateProposalEntityId, + generateStandardCallbackEntityId, + generateTransactionActionsProposalEntityId, +} from '@aragon/osx-commons-subgraph'; +import {BigInt, Bytes, store} from '@graphprotocol/graph-ts'; export function handleMetadataSet(event: MetadataSet): void { - let daoId = event.address.toHexString(); + let daoId = generateDaoEntityId(event.address); let metadata = event.params.metadata.toString(); _handleMetadataSet(daoId, metadata); } @@ -81,16 +83,16 @@ export function handleCallbackReceived(event: CallbackReceived): void { } export function handleExecuted(event: Executed): void { - let proposalId = event.params.actor - .toHexString() - .concat('_') - .concat(event.params.callId.toHexString()); + let proposalEntityId = generateProposalEntityId( + event.params.actor, + BigInt.fromByteArray(event.params.callId) + ); // Not an effective solution, until each plugin has // its own subgraph separately. // If proposal found, update its failureMap. let wasUpdated = updateProposalWithFailureMap( - proposalId, + proposalEntityId, event.params.failureMap ); @@ -105,13 +107,14 @@ export function handleExecuted(event: Executed): void { // might be a case when 2 different tx might end up having the same // proposal ID which will cause overwrite. In case of plugins, // It's plugin's responsibility to pass unique callId per execute. - proposalId = proposalId - .concat('_') - .concat(event.transaction.hash.toHexString()) - .concat('_') - .concat(event.transactionLogIndex.toHexString()); - let proposal = new TransactionActionsProposal(proposalId); - proposal.dao = event.address.toHexString(); + proposalEntityId = generateTransactionActionsProposalEntityId( + proposalEntityId, + event.transaction.hash, + event.logIndex + ); + + let proposal = new TransactionActionsProposal(proposalEntityId); + proposal.dao = generateDaoEntityId(event.address); proposal.createdAt = event.block.timestamp; proposal.endDate = event.block.timestamp; proposal.startDate = event.block.timestamp; @@ -128,7 +131,7 @@ export function handleExecuted(event: Executed): void { let actions = event.params.actions; for (let index = 0; index < actions.length; index++) { - handleAction(actions[index], proposalId, index, event); + handleAction(actions[index], proposalEntityId, index, event); } } @@ -164,54 +167,55 @@ export function handleNativeTokenDeposited(event: NativeTokenDeposited): void { } export function handleGranted(event: Granted): void { - // ContractPermissionId - let daoId = event.address.toHexString(); - let contractPermissionIdEntityId = - event.params.where.toHexString() + - '_' + - event.params.permissionId.toHexString(); - let contractPermissionIdEntity = ContractPermissionId.load( - contractPermissionIdEntityId + const contractAddress = event.address; + const where = event.params.where; + const permissionId = event.params.permissionId; + const who = event.params.who; + + const permissionEntityId = generatePermissionEntityId( + contractAddress, + permissionId, + where, + who ); - if (!contractPermissionIdEntity) { - contractPermissionIdEntity = new ContractPermissionId( - contractPermissionIdEntityId - ); - contractPermissionIdEntity.dao = daoId; - contractPermissionIdEntity.where = event.params.where; - contractPermissionIdEntity.permissionId = event.params.permissionId; - contractPermissionIdEntity.save(); - } // Permission - let permissionId = - contractPermissionIdEntityId + '_' + event.params.who.toHexString(); - let permissionEntity = new Permission(permissionId); - permissionEntity.dao = daoId; - permissionEntity.contractPermissionId = contractPermissionIdEntityId; - permissionEntity.where = event.params.where; - permissionEntity.who = event.params.who; - permissionEntity.actor = event.params.here; - permissionEntity.condition = event.params.condition; - permissionEntity.save(); + let permissionEntity = Permission.load(permissionEntityId); + if (!permissionEntity) { + permissionEntity = new Permission(permissionEntityId); + permissionEntity.where = where; + permissionEntity.permissionId = permissionId; + permissionEntity.who = who; + permissionEntity.actor = event.params.here; + permissionEntity.condition = event.params.condition; + + permissionEntity.dao = generateDaoEntityId(contractAddress); + permissionEntity.save(); + } } export function handleRevoked(event: Revoked): void { // permission - let permissionId = - event.params.where.toHexString() + - '_' + - event.params.permissionId.toHexString() + - '_' + - event.params.who.toHexString(); - let permissionEntity = Permission.load(permissionId); + const contractAddress = event.address; + const where = event.params.where; + const permissionId = event.params.permissionId; + const who = event.params.who; + + const permissionEntityId = generatePermissionEntityId( + contractAddress, + permissionId, + where, + who + ); + + let permissionEntity = Permission.load(permissionEntityId); if (permissionEntity) { - store.remove('Permission', permissionId); + store.remove('Permission', permissionEntityId); } } export function handleTrustedForwarderSet(event: TrustedForwarderSet): void { - let daoId = event.address.toHexString(); + let daoId = generateDaoEntityId(event.address); let entity = Dao.load(daoId); if (entity) { entity.trustedForwarder = event.params.forwarder; @@ -219,36 +223,28 @@ export function handleTrustedForwarderSet(event: TrustedForwarderSet): void { } } -export function handleSignatureValidatorSet( - event: SignatureValidatorSet -): void { - let daoId = event.address.toHexString(); - let entity = Dao.load(daoId); - if (entity) { - entity.signatureValidator = event.params.signatureValidator; - entity.save(); - } -} - export function handleStandardCallbackRegistered( event: StandardCallbackRegistered ): void { - let daoId = event.address.toHexString(); - let entityId = `${daoId}_${event.params.interfaceId.toHexString()}`; + let daoAddress = event.address; + let interfaceId = event.params.interfaceId; + + let daoId = generateDaoEntityId(daoAddress); + let entityId = generateStandardCallbackEntityId(daoAddress, interfaceId); let entity = StandardCallback.load(entityId); if (!entity) { entity = new StandardCallback(entityId); entity.dao = daoId; } - entity.interfaceId = event.params.interfaceId; + entity.interfaceId = interfaceId; entity.callbackSelector = event.params.callbackSelector; entity.magicNumber = event.params.magicNumber; entity.save(); } export function handleNewURI(event: NewURI): void { - let daoId = event.address.toHexString(); - let entity = Dao.load(daoId); + let daoEntityId = generateDaoEntityId(event.address); + let entity = Dao.load(daoEntityId); if (entity) { entity.daoURI = event.params.daoURI; entity.save(); diff --git a/packages/subgraph/src/dao/dao_v1_3_0.ts b/packages/subgraph/src/dao/dao_v1_3_0.ts index e362a40dc..ed075809f 100644 --- a/packages/subgraph/src/dao/dao_v1_3_0.ts +++ b/packages/subgraph/src/dao/dao_v1_3_0.ts @@ -1,23 +1,32 @@ import {TransactionActionsProposal} from '../../generated/schema'; import { Executed, - ExecutedActionsStruct + ExecutedActionsStruct, } from '../../generated/templates/DaoTemplateV1_3_0/DAO'; import {handleAction} from './utils'; +import { + generateDaoEntityId, + generateProposalEntityId, + generateTransactionActionsProposalEntityId, +} from '@aragon/osx-commons-subgraph'; +import {BigInt} from '@graphprotocol/graph-ts'; export function handleExecuted(event: Executed): void { - let proposalId = event.params.actor - .toHexString() - .concat('_') - .concat(event.params.callId.toHexString()); + let proposalEntityId = generateProposalEntityId( + event.params.actor, + BigInt.fromByteArray(event.params.callId) + ); - proposalId = proposalId - .concat('_') - .concat(event.transaction.hash.toHexString()) - .concat('_') - .concat(event.transactionLogIndex.toHexString()); - let proposal = new TransactionActionsProposal(proposalId); - proposal.dao = event.address.toHexString(); + let transactionActionsProposalEntityId = + generateTransactionActionsProposalEntityId( + proposalEntityId, + event.transaction.hash, + event.transactionLogIndex + ); + let proposal = new TransactionActionsProposal( + transactionActionsProposalEntityId + ); + proposal.dao = generateDaoEntityId(event.address); proposal.createdAt = event.block.timestamp; proposal.endDate = event.block.timestamp; proposal.startDate = event.block.timestamp; @@ -33,7 +42,7 @@ export function handleExecuted(event: Executed): void { for (let index = 0; index < actions.length; index++) { handleAction( actions[index], - proposalId, + transactionActionsProposalEntityId, index, event ); diff --git a/packages/subgraph/src/dao/utils.ts b/packages/subgraph/src/dao/utils.ts index a8833d445..a1d6e6fa6 100644 --- a/packages/subgraph/src/dao/utils.ts +++ b/packages/subgraph/src/dao/utils.ts @@ -1,15 +1,15 @@ -import {BigInt} from '@graphprotocol/graph-ts'; import { Action, AddresslistVotingProposal, AdminProposal, MultisigProposal, - TokenVotingProposal + TokenVotingProposal, } from '../../generated/schema'; import { Executed, - ExecutedActionsStruct + ExecutedActionsStruct, } from '../../generated/templates/DaoTemplateV1_0_0/DAO'; +import {getMethodSignature} from '../utils/bytes'; import { ERC721_transferFrom, ERC721_safeTransferFromNoData, @@ -17,14 +17,16 @@ import { ERC20_transfer, ERC20_transferFrom, ERC1155_safeBatchTransferFrom, - ERC1155_safeTransferFrom + ERC1155_safeTransferFrom, } from '../utils/tokens/common'; import {handleERC20Action} from '../utils/tokens/erc20'; import {handleERC721Action} from '../utils/tokens/erc721'; -import {handleNativeAction} from '../utils/tokens/eth'; import {handleERC1155Action} from '../utils/tokens/erc1155'; +import {handleNativeAction} from '../utils/tokens/eth'; +import {generateDaoEntityId} from '@aragon/osx-commons-subgraph'; +import {BigInt} from '@graphprotocol/graph-ts'; -// AssemblyScript struggles having mutliple return types. Due to this, +// AssemblyScript struggles having multiple return types. Due to this, // The below seems most effective way. export function updateProposalWithFailureMap( proposalId: string, @@ -65,26 +67,11 @@ export function handleAction< T extends ExecutedActionsStruct, R extends Executed >(action: T, proposalId: string, index: i32, event: R): void { - let actionId = proposalId.concat('_').concat(index.toString()); - let actionEntity = Action.load(actionId); - - // In case the execute on the dao is called by the address - // That we don't currently index for the actions in the subgraph, - // we fallback and still create an action. - // NOTE that it's important to generate action id differently to not allow overwriting. - if (!actionEntity) { - actionEntity = new Action(actionId); - actionEntity.to = action.to; - actionEntity.value = action.value; - actionEntity.data = action.data; - actionEntity.proposal = proposalId; - actionEntity.dao = event.address.toHexString(); - } - + let actionEntity = getOrCreateActionEntity(action, proposalId, index, event); actionEntity.execResult = event.params.execResults[index]; actionEntity.save(); - if (action.data.toHexString() == '0x' && action.value.gt(BigInt.zero())) { + if (isNativeTokenAction(action)) { handleNativeAction( event.address, action.to, @@ -97,48 +84,94 @@ export function handleAction< return; } - let methodSig = action.data.toHexString().slice(0, 10); - - // Since ERC721 transferFrom and ERC20 transferFrom have the same signature, - // The below first checks if it's ERC721 by calling `supportsInterface` and then - // moves to ERC20 check. Currently, if `action` is transferFrom, it will still check - // both `handleERC721Action`, `handleERC20Action`. - if ( - methodSig == ERC721_transferFrom || - methodSig == ERC721_safeTransferFromNoData || - methodSig == ERC721_safeTransferFromWithData - ) { - handleERC721Action( + handleTokenTransfers(action, proposalId, index, event); +} + +function getOrCreateActionEntity< + T extends ExecutedActionsStruct, + R extends Executed +>(action: T, proposalId: string, index: i32, event: R): Action { + const actionId = [proposalId, index.toString()].join('_'); + let entity = Action.load(actionId); + + // In case the execute on the dao is called by the address + // That we don't currently index for the actions in the subgraph, + // we fallback and still create an action. + // NOTE that it's important to generate action id differently to not allow + + if (!entity) { + entity = new Action(actionId); + entity.to = action.to; + entity.value = action.value; + entity.data = action.data; + entity.proposal = proposalId; + entity.dao = generateDaoEntityId(event.address); + } + + return entity; +} + +function handleTokenTransfers< + T extends ExecutedActionsStruct, + R extends Executed +>(action: T, proposalId: string, actionIndex: i32, event: R): void { + const methodSig = getMethodSignature(action.data); + + let handledByErc721: bool = false; + let handledByErc1155: bool = false; + + if (isERC721Transfer(methodSig)) { + handledByErc721 = handleERC721Action( action.to, event.address, action.data, proposalId, - index, + actionIndex, event ); } - if ( - methodSig == ERC1155_safeBatchTransferFrom || - methodSig == ERC1155_safeTransferFrom - ) { - handleERC1155Action( + + if (isERC1155TransferMethod(methodSig)) { + handledByErc1155 = handleERC1155Action( action.to, event.address, action.data, proposalId, - index, + actionIndex, event ); } - if (methodSig == ERC20_transfer || methodSig == ERC20_transferFrom) { + if (isERC20Transfer(methodSig) && !handledByErc721 && !handledByErc1155) { handleERC20Action( action.to, event.address, proposalId, action.data, - index, + actionIndex, event ); } } + +function isERC721Transfer(methodSig: string): bool { + return [ + ERC721_transferFrom, + ERC721_safeTransferFromNoData, + ERC721_safeTransferFromWithData, + ].includes(methodSig); +} + +function isERC20Transfer(methodSig: string): bool { + return [ERC20_transfer, ERC20_transferFrom].includes(methodSig); +} + +function isNativeTokenAction(action: T): bool { + return action.data.toHexString() === '0x' && action.value.gt(BigInt.zero()); +} + +function isERC1155TransferMethod(methodSig: string): bool { + return [ERC1155_safeBatchTransferFrom, ERC1155_safeTransferFrom].includes( + methodSig + ); +} diff --git a/packages/subgraph/src/packages/addresslist/addresslist-voting.ts b/packages/subgraph/src/packages/addresslist/addresslist-voting.ts index 0fd543684..f8b894406 100644 --- a/packages/subgraph/src/packages/addresslist/addresslist-voting.ts +++ b/packages/subgraph/src/packages/addresslist/addresslist-voting.ts @@ -1,23 +1,28 @@ -import {BigInt, dataSource, store} from '@graphprotocol/graph-ts'; - -import { - VoteCast, - ProposalCreated, - ProposalExecuted, - VotingSettingsUpdated, - MembersAdded, - MembersRemoved, - AddresslistVoting -} from '../../../generated/templates/AddresslistVoting/AddresslistVoting'; import { Action, AddresslistVotingPlugin, AddresslistVotingProposal, + AddresslistVotingVote, AddresslistVotingVoter, - AddresslistVotingVote } from '../../../generated/schema'; +import { + AddresslistVoting, + MembersAdded, + MembersRemoved, + ProposalCreated, + ProposalExecuted, + VoteCast, + VotingSettingsUpdated, +} from '../../../generated/templates/AddresslistVoting/AddresslistVoting'; import {RATIO_BASE, VOTER_OPTIONS, VOTING_MODES} from '../../utils/constants'; -import {getProposalId} from '../../utils/proposals'; +import {generateMemberEntityId, generateVoteEntityId} from '../../utils/ids'; +import { + generateActionEntityId, + generateEntityIdFromAddress, + generatePluginEntityId, + generateProposalEntityId, +} from '@aragon/osx-commons-subgraph'; +import {BigInt, dataSource, store} from '@graphprotocol/graph-ts'; export function handleProposalCreated(event: ProposalCreated): void { let context = dataSource.context(); @@ -33,19 +38,20 @@ export function _handleProposalCreated( metadata: string ): void { let pluginAddress = event.address; + let pluginEntityId = generatePluginEntityId(pluginAddress); let pluginProposalId = event.params.proposalId; - let proposalId = getProposalId(pluginAddress, pluginProposalId); + let proposalId = generateProposalEntityId(pluginAddress, pluginProposalId); let proposalEntity = new AddresslistVotingProposal(proposalId); proposalEntity.dao = daoId; - proposalEntity.plugin = pluginAddress.toHexString(); + proposalEntity.plugin = pluginEntityId; proposalEntity.pluginProposalId = pluginProposalId; proposalEntity.creator = event.params.creator; proposalEntity.metadata = metadata; proposalEntity.createdAt = event.block.timestamp; proposalEntity.creationBlockNumber = event.block.number; proposalEntity.allowFailureMap = event.params.allowFailureMap; - proposalEntity.potentiallyExecutable = false; + proposalEntity.approvalReached = false; let contract = AddresslistVoting.bind(pluginAddress); let proposal = contract.try_getProposal(pluginProposalId); @@ -76,10 +82,7 @@ export function _handleProposalCreated( let actions = proposal.value.value4; for (let index = 0; index < actions.length; index++) { const action = actions[index]; - - let actionId = getProposalId(pluginAddress, pluginProposalId) - .concat('_') - .concat(index.toString()); + let actionId = generateActionEntityId(proposalId, index); let actionEntity = new Action(actionId); actionEntity.to = action.to; @@ -89,7 +92,7 @@ export function _handleProposalCreated( actionEntity.proposal = proposalId; actionEntity.save(); } - + proposalEntity.isSignaling = actions.length == 0; // totalVotingPower proposalEntity.totalVotingPower = contract.try_totalVotingPower( parameters.snapshotBlock @@ -111,12 +114,12 @@ export function _handleProposalCreated( export function handleVoteCast(event: VoteCast): void { const pluginProposalId = event.params.proposalId; - const member = event.params.voter.toHexString(); + const memberAddress = event.params.voter; const pluginAddress = event.address; - const pluginId = pluginAddress.toHexString(); - const memberId = pluginId.concat('_').concat(member); - let proposalId = getProposalId(pluginAddress, pluginProposalId); - let voterVoteId = member.concat('_').concat(proposalId); + const memberEntityId = generateMemberEntityId(pluginAddress, memberAddress); + + let proposalId = generateProposalEntityId(pluginAddress, pluginProposalId); + let voterVoteId = generateVoteEntityId(memberAddress, proposalId); let voteOption = VOTER_OPTIONS.get(event.params.voteOption); if (voteOption === 'None') { @@ -129,7 +132,7 @@ export function handleVoteCast(event: VoteCast): void { voterProposalVoteEntity.updatedAt = event.block.timestamp; } else { voterProposalVoteEntity = new AddresslistVotingVote(voterVoteId); - voterProposalVoteEntity.voter = memberId; + voterProposalVoteEntity.voter = memberEntityId; voterProposalVoteEntity.proposal = proposalId; voterProposalVoteEntity.createdAt = event.block.timestamp; voterProposalVoteEntity.voteReplaced = false; @@ -186,7 +189,7 @@ export function handleVoteCast(event: VoteCast): void { let minParticipationReached = castedVotingPower.ge(minVotingPower); // Used when proposal has ended. - proposalEntity.potentiallyExecutable = + proposalEntity.approvalReached = supportThresholdReached && minParticipationReached; // Used when proposal has not ended. @@ -202,12 +205,12 @@ export function handleVoteCast(event: VoteCast): void { export function handleProposalExecuted(event: ProposalExecuted): void { let pluginProposalId = event.params.proposalId; - let proposalId = getProposalId(event.address, pluginProposalId); + let proposalId = generateProposalEntityId(event.address, pluginProposalId); let proposalEntity = AddresslistVotingProposal.load(proposalId); if (proposalEntity) { proposalEntity.executed = true; - proposalEntity.potentiallyExecutable = false; + proposalEntity.approvalReached = true; proposalEntity.executionDate = event.block.timestamp; proposalEntity.executionBlockNumber = event.block.number; proposalEntity.executionTxHash = event.transaction.hash; @@ -218,7 +221,9 @@ export function handleProposalExecuted(event: ProposalExecuted): void { export function handleVotingSettingsUpdated( event: VotingSettingsUpdated ): void { - let packageEntity = AddresslistVotingPlugin.load(event.address.toHexString()); + let pluginAddress = event.address; + let pluginEntityId = generatePluginEntityId(pluginAddress); + let packageEntity = AddresslistVotingPlugin.load(pluginEntityId); if (packageEntity) { packageEntity.votingMode = VOTING_MODES.get(event.params.votingMode); packageEntity.supportThreshold = event.params.supportThreshold; @@ -232,15 +237,15 @@ export function handleVotingSettingsUpdated( export function handleMembersAdded(event: MembersAdded): void { let members = event.params.members; for (let index = 0; index < members.length; index++) { - const member = members[index].toHexString(); - const pluginId = event.address.toHexString(); - const memberId = pluginId + '_' + member; - - let voterEntity = AddresslistVotingVoter.load(memberId); + const memberAddress = members[index]; + const pluginAddress = event.address; + const pluginEntityId = generatePluginEntityId(pluginAddress); + const memberEntityId = generateMemberEntityId(pluginAddress, memberAddress); + let voterEntity = AddresslistVotingVoter.load(memberEntityId); if (!voterEntity) { - voterEntity = new AddresslistVotingVoter(memberId); - voterEntity.address = member; - voterEntity.plugin = pluginId; + voterEntity = new AddresslistVotingVoter(memberEntityId); + voterEntity.address = generateEntityIdFromAddress(memberAddress); + voterEntity.plugin = pluginEntityId; voterEntity.save(); } } @@ -249,13 +254,13 @@ export function handleMembersAdded(event: MembersAdded): void { export function handleMembersRemoved(event: MembersRemoved): void { let members = event.params.members; for (let index = 0; index < members.length; index++) { - const member = members[index].toHexString(); - const pluginId = event.address.toHexString(); - const memberId = pluginId + '_' + member; + const memberAddress = members[index]; + let pluginAddress = event.address; + const memberEntityId = generateMemberEntityId(pluginAddress, memberAddress); - let voterEntity = AddresslistVotingVoter.load(memberId); + let voterEntity = AddresslistVotingVoter.load(memberEntityId); if (voterEntity) { - store.remove('AddresslistVotingVoter', memberId); + store.remove('AddresslistVotingVoter', memberEntityId); } } } diff --git a/packages/subgraph/src/packages/admin/admin.ts b/packages/subgraph/src/packages/admin/admin.ts index 13225955f..411a4e9d7 100644 --- a/packages/subgraph/src/packages/admin/admin.ts +++ b/packages/subgraph/src/packages/admin/admin.ts @@ -1,18 +1,22 @@ -import {dataSource, DataSourceContext} from '@graphprotocol/graph-ts'; - -import { - MembershipContractAnnounced, - ProposalCreated, - ProposalExecuted -} from '../../../generated/templates/Admin/Admin'; import { Action, AdministratorAdminPlugin, AdminProposal, - Administrator + Administrator, } from '../../../generated/schema'; import {AdminMembers} from '../../../generated/templates'; -import {getProposalId} from '../../utils/proposals'; +import { + MembershipContractAnnounced, + ProposalCreated, + ProposalExecuted, +} from '../../../generated/templates/Admin/Admin'; +import {generateAdministratorAdminPluginEntityId} from '../../utils/ids'; +import { + generateActionEntityId, + generatePluginEntityId, + generateProposalEntityId, +} from '@aragon/osx-commons-subgraph'; +import {dataSource, DataSourceContext} from '@graphprotocol/graph-ts'; export function handleProposalCreated(event: ProposalCreated): void { let context = dataSource.context(); @@ -28,13 +32,17 @@ export function _handleProposalCreated( metadata: string ): void { let pluginProposalId = event.params.proposalId; - let proposalId = getProposalId(event.address, pluginProposalId); - let pluginId = event.address.toHexString(); + let pluginAddress = event.address; + let pluginEntityId = generatePluginEntityId(pluginAddress); + let proposalEntityId = generateProposalEntityId( + pluginAddress, + pluginProposalId + ); let administratorAddress = event.params.creator; - let proposalEntity = new AdminProposal(proposalId); + let proposalEntity = new AdminProposal(proposalEntityId); proposalEntity.dao = daoId; - proposalEntity.plugin = pluginId; + proposalEntity.plugin = pluginEntityId; proposalEntity.pluginProposalId = pluginProposalId; proposalEntity.creator = administratorAddress; proposalEntity.metadata = metadata; @@ -44,17 +52,15 @@ export function _handleProposalCreated( proposalEntity.endDate = event.params.endDate; proposalEntity.administrator = administratorAddress.toHexString(); proposalEntity.allowFailureMap = event.params.allowFailureMap; - - // Administrator - let administratorId = administratorAddress - .toHexString() - .concat('_') - .concat(pluginId); - let adminMemberEntity = AdministratorAdminPlugin.load(administratorId); + let administratorEntityId = generateAdministratorAdminPluginEntityId( + administratorAddress, + pluginAddress + ); + let adminMemberEntity = AdministratorAdminPlugin.load(administratorEntityId); if (!adminMemberEntity) { - adminMemberEntity = new AdministratorAdminPlugin(administratorId); + adminMemberEntity = new AdministratorAdminPlugin(administratorEntityId); adminMemberEntity.administrator = administratorAddress.toHexString(); - adminMemberEntity.plugin = pluginId; + adminMemberEntity.plugin = pluginEntityId; adminMemberEntity.save(); } let administratorEntity = Administrator.load( @@ -71,16 +77,13 @@ export function _handleProposalCreated( for (let index = 0; index < actions.length; index++) { const action = actions[index]; - let actionId = getProposalId(event.address, pluginProposalId) - .concat('_') - .concat(index.toString()); - - let actionEntity = new Action(actionId); + let actionEntityId = generateActionEntityId(proposalEntityId, index); + let actionEntity = new Action(actionEntityId); actionEntity.to = action.to; actionEntity.value = action.value; actionEntity.data = action.data; actionEntity.dao = daoId; - actionEntity.proposal = proposalId; + actionEntity.proposal = proposalEntityId; actionEntity.save(); } @@ -89,9 +92,12 @@ export function _handleProposalCreated( export function handleProposalExecuted(event: ProposalExecuted): void { let pluginProposalId = event.params.proposalId; - let proposalId = getProposalId(event.address, pluginProposalId); + let proposalEntityId = generateProposalEntityId( + event.address, + pluginProposalId + ); - let proposalEntity = AdminProposal.load(proposalId); + let proposalEntity = AdminProposal.load(proposalEntityId); if (proposalEntity) { proposalEntity.executed = true; proposalEntity.executionTxHash = event.transaction.hash; diff --git a/packages/subgraph/src/packages/admin/adminMembers.ts b/packages/subgraph/src/packages/admin/adminMembers.ts index 4f6f2019a..fd7f46ee1 100644 --- a/packages/subgraph/src/packages/admin/adminMembers.ts +++ b/packages/subgraph/src/packages/admin/adminMembers.ts @@ -1,9 +1,14 @@ -import {dataSource, store} from '@graphprotocol/graph-ts'; import { Administrator, - AdministratorAdminPlugin + AdministratorAdminPlugin, } from '../../../generated/schema'; import {Granted, Revoked} from '../../../generated/templates/Admin/DAO'; +import {generateAdministratorAdminPluginEntityId} from '../../utils/ids'; +import { + generateEntityIdFromAddress, + generatePluginEntityId, +} from '@aragon/osx-commons-subgraph'; +import {dataSource, store} from '@graphprotocol/graph-ts'; export function handleGranted(event: Granted): void { if ( @@ -12,16 +17,22 @@ export function handleGranted(event: Granted): void { event.params.where.toHexString() ) ) { - let pluginAddress = event.params.where.toHexString(); - let administratorAddress = event.params.who.toHexString(); - let administrator = Administrator.load(administratorAddress); + let pluginAddress = event.params.where; + let administratorAddress = event.params.who; + let pluginEntityId = generatePluginEntityId(pluginAddress); + let administratorEntityId = + generateEntityIdFromAddress(administratorAddress); + let administrator = Administrator.load(administratorEntityId); if (!administrator) { - administrator = new Administrator(administratorAddress); - administrator.address = administratorAddress; + administrator = new Administrator(administratorEntityId); + administrator.address = administratorEntityId; administrator.save(); } - let administratorMappingId = `${pluginAddress}_${administratorAddress}`; + let administratorMappingId = generateAdministratorAdminPluginEntityId( + pluginAddress, + administratorAddress + ); let administratorPluginMapping = AdministratorAdminPlugin.load( administratorMappingId ); @@ -29,8 +40,8 @@ export function handleGranted(event: Granted): void { administratorPluginMapping = new AdministratorAdminPlugin( administratorMappingId ); - administratorPluginMapping.administrator = administratorAddress; - administratorPluginMapping.plugin = pluginAddress; + administratorPluginMapping.administrator = administratorEntityId; + administratorPluginMapping.plugin = pluginEntityId; administratorPluginMapping.save(); } } @@ -43,7 +54,12 @@ export function handleRevoked(event: Revoked): void { event.params.where.toHexString() ) ) { - let mappingId = `${event.params.where.toHexString()}_${event.params.who.toHexString()}`; + // where is the plugin address + // who is the administrator address + let mappingId = generateAdministratorAdminPluginEntityId( + event.params.where, + event.params.who + ); if (AdministratorAdminPlugin.load(mappingId)) { store.remove('AdministratorAdminPlugin', mappingId); } diff --git a/packages/subgraph/src/packages/multisig/multisig.ts b/packages/subgraph/src/packages/multisig/multisig.ts index 412719d8e..04ca24484 100644 --- a/packages/subgraph/src/packages/multisig/multisig.ts +++ b/packages/subgraph/src/packages/multisig/multisig.ts @@ -1,5 +1,10 @@ -import {dataSource, store} from '@graphprotocol/graph-ts'; - +import { + Action, + MultisigPlugin, + MultisigProposal, + MultisigApprover, + MultisigProposalApprover, +} from '../../../generated/schema'; import { ProposalCreated, ProposalExecuted, @@ -7,16 +12,15 @@ import { MembersRemoved, Multisig, Approved, - MultisigSettingsUpdated + MultisigSettingsUpdated, } from '../../../generated/templates/Multisig/Multisig'; +import {generateMemberEntityId, generateVoterEntityId} from '../../utils/ids'; import { - Action, - MultisigPlugin, - MultisigProposal, - MultisigApprover, - MultisigProposalApprover -} from '../../../generated/schema'; -import {getProposalId} from '../../utils/proposals'; + generateActionEntityId, + generatePluginEntityId, + generateProposalEntityId, +} from '@aragon/osx-commons-subgraph'; +import {dataSource, store} from '@graphprotocol/graph-ts'; export function handleProposalCreated(event: ProposalCreated): void { let context = dataSource.context(); @@ -32,11 +36,16 @@ export function _handleProposalCreated( metadata: string ): void { let pluginProposalId = event.params.proposalId; - let proposalId = getProposalId(event.address, pluginProposalId); + let pluginAddress = event.address; + let proposalEntityId = generateProposalEntityId( + pluginAddress, + pluginProposalId + ); + let pluginEntityId = generatePluginEntityId(pluginAddress); - let proposalEntity = new MultisigProposal(proposalId); + let proposalEntity = new MultisigProposal(proposalEntityId); proposalEntity.dao = daoId; - proposalEntity.plugin = event.address.toHexString(); + proposalEntity.plugin = pluginEntityId; proposalEntity.pluginProposalId = pluginProposalId; proposalEntity.creator = event.params.creator; proposalEntity.metadata = metadata; @@ -46,48 +55,41 @@ export function _handleProposalCreated( proposalEntity.endDate = event.params.endDate; proposalEntity.allowFailureMap = event.params.allowFailureMap; - let contract = Multisig.bind(event.address); - let vote = contract.try_getProposal(pluginProposalId); + let contract = Multisig.bind(pluginAddress); + let proposal = contract.try_getProposal(pluginProposalId); - if (!vote.reverted) { - proposalEntity.executed = vote.value.value0; - proposalEntity.approvals = vote.value.value1; + if (!proposal.reverted) { + proposalEntity.executed = proposal.value.value0; + proposalEntity.approvals = proposal.value.value1; // ProposalParameters - let parameters = vote.value.value2; + let parameters = proposal.value.value2; proposalEntity.minApprovals = parameters.minApprovals; proposalEntity.snapshotBlock = parameters.snapshotBlock; - - // if minApproval is 1, the proposal is always executable - if (parameters.minApprovals == 1) { - proposalEntity.potentiallyExecutable = true; - } else { - proposalEntity.potentiallyExecutable = false; - } + proposalEntity.approvalReached = false; // Actions - let actions = vote.value.value3; + let actions = proposal.value.value3; for (let index = 0; index < actions.length; index++) { const action = actions[index]; - let actionId = getProposalId(event.address, pluginProposalId) - .concat('_') - .concat(index.toString()); + let actionId = generateActionEntityId(proposalEntityId, index); let actionEntity = new Action(actionId); actionEntity.to = action.to; actionEntity.value = action.value; actionEntity.data = action.data; actionEntity.dao = daoId; - actionEntity.proposal = proposalId; + actionEntity.proposal = proposalEntityId; actionEntity.save(); } + proposalEntity.isSignaling = actions.length == 0; } proposalEntity.save(); // update vote length - let packageEntity = MultisigPlugin.load(event.address.toHexString()); + let packageEntity = MultisigPlugin.load(pluginEntityId); if (packageEntity) { let voteLength = contract.try_proposalCount(); if (!voteLength.reverted) { @@ -98,28 +100,33 @@ export function _handleProposalCreated( } export function handleApproved(event: Approved): void { - const member = event.params.approver.toHexString(); - const pluginId = event.address.toHexString(); - const memberId = pluginId.concat('_').concat(member); + let memberAddress = event.params.approver; + let pluginAddress = event.address; + let memberEntityId = generateMemberEntityId(pluginAddress, memberAddress); let pluginProposalId = event.params.proposalId; - let proposalId = getProposalId(event.address, pluginProposalId); - let approverProposalId = member.concat('_').concat(proposalId); - - let approverProposalEntity = MultisigProposalApprover.load( - approverProposalId + let proposalEntityId = generateProposalEntityId( + event.address, + pluginProposalId + ); + let approverProposalId = generateVoterEntityId( + memberEntityId, + proposalEntityId ); + + let approverProposalEntity = + MultisigProposalApprover.load(approverProposalId); if (!approverProposalEntity) { approverProposalEntity = new MultisigProposalApprover(approverProposalId); - approverProposalEntity.approver = memberId; - approverProposalEntity.proposal = proposalId; + approverProposalEntity.approver = memberEntityId; + approverProposalEntity.proposal = proposalEntityId; } approverProposalEntity.createdAt = event.block.timestamp; approverProposalEntity.save(); // update count - let proposalEntity = MultisigProposal.load(proposalId); + let proposalEntity = MultisigProposal.load(proposalEntityId); if (proposalEntity) { - let contract = Multisig.bind(event.address); + let contract = Multisig.bind(pluginAddress); let proposal = contract.try_getProposal(pluginProposalId); if (!proposal.reverted) { @@ -131,9 +138,9 @@ export function handleApproved(event: Approved): void { if ( approvals >= minApprovalsStruct.minApprovals && - !proposalEntity.potentiallyExecutable + !proposalEntity.approvalReached ) { - proposalEntity.potentiallyExecutable = true; + proposalEntity.approvalReached = true; } proposalEntity.save(); @@ -143,11 +150,14 @@ export function handleApproved(event: Approved): void { export function handleProposalExecuted(event: ProposalExecuted): void { let pluginProposalId = event.params.proposalId; - let proposalId = getProposalId(event.address, pluginProposalId); + let proposalEntityId = generateProposalEntityId( + event.address, + pluginProposalId + ); - let proposalEntity = MultisigProposal.load(proposalId); + let proposalEntity = MultisigProposal.load(proposalEntityId); if (proposalEntity) { - proposalEntity.potentiallyExecutable = false; + proposalEntity.approvalReached = false; proposalEntity.executed = true; proposalEntity.executionDate = event.block.timestamp; proposalEntity.executionBlockNumber = event.block.number; @@ -159,15 +169,17 @@ export function handleProposalExecuted(event: ProposalExecuted): void { export function handleMembersAdded(event: MembersAdded): void { const members = event.params.members; for (let index = 0; index < members.length; index++) { - const member = members[index].toHexString(); - const pluginId = event.address.toHexString(); - const memberId = pluginId + '_' + member; + const memberAddress = members[index]; + const pluginEntityId = generatePluginEntityId(event.address); + const memberEntityId = [pluginEntityId, memberAddress.toHexString()].join( + '_' + ); - let approverEntity = MultisigApprover.load(memberId); + let approverEntity = MultisigApprover.load(memberEntityId); if (!approverEntity) { - approverEntity = new MultisigApprover(memberId); - approverEntity.address = member; - approverEntity.plugin = pluginId; + approverEntity = new MultisigApprover(memberEntityId); + approverEntity.address = memberAddress.toHexString(); + approverEntity.plugin = pluginEntityId; approverEntity.save(); } } @@ -176,13 +188,15 @@ export function handleMembersAdded(event: MembersAdded): void { export function handleMembersRemoved(event: MembersRemoved): void { const members = event.params.members; for (let index = 0; index < members.length; index++) { - const member = members[index].toHexString(); - const pluginId = event.address.toHexString(); - const memberId = pluginId + '_' + member; + const memberAddress = members[index]; + const pluginEntityId = generatePluginEntityId(event.address); + const memberEntityId = [pluginEntityId, memberAddress.toHexString()].join( + '_' + ); - const approverEntity = MultisigApprover.load(memberId); + const approverEntity = MultisigApprover.load(memberEntityId); if (approverEntity) { - store.remove('MultisigApprover', memberId); + store.remove('MultisigApprover', memberEntityId); } } } @@ -190,7 +204,9 @@ export function handleMembersRemoved(event: MembersRemoved): void { export function handleMultisigSettingsUpdated( event: MultisigSettingsUpdated ): void { - let packageEntity = MultisigPlugin.load(event.address.toHexString()); + let packageEntity = MultisigPlugin.load( + generatePluginEntityId(event.address) + ); if (packageEntity) { packageEntity.onlyListed = event.params.onlyListed; packageEntity.minApprovals = event.params.minApprovals; diff --git a/packages/subgraph/src/packages/token/governance-erc20.ts b/packages/subgraph/src/packages/token/governance-erc20.ts index 3e7f61e05..575c07c4e 100644 --- a/packages/subgraph/src/packages/token/governance-erc20.ts +++ b/packages/subgraph/src/packages/token/governance-erc20.ts @@ -1,44 +1,69 @@ -import {Address, BigInt, dataSource, store} from '@graphprotocol/graph-ts'; - import {TokenVotingMember} from '../../../generated/schema'; -import {Transfer} from '../../../generated/templates/TokenVoting/ERC20'; +import {GovernanceERC20 as GovernanceERC20Contract} from '../../../generated/templates/GovernanceERC20/GovernanceERC20'; import { DelegateChanged, - DelegateVotesChanged + DelegateVotesChanged, } from '../../../generated/templates/GovernanceERC20/GovernanceERC20'; +import {Transfer} from '../../../generated/templates/TokenVoting/ERC20'; +import {generateMemberEntityId} from '../../utils/ids'; +import { + TokenVotingMemberResult, + getDelegateeId, + getERC20Balance, + getVotingPower, +} from './utils'; +import {Address, BigInt, dataSource, store} from '@graphprotocol/graph-ts'; + +function getOrCreateMember( + user: Address, + pluginId: string, + tokenAddress: Address +): TokenVotingMemberResult { + let memberEntityId = generateMemberEntityId( + Address.fromString(pluginId), + user + ); + let createdNew = false; + let member = TokenVotingMember.load(memberEntityId); -function getOrCreateMember(user: Address, pluginId: string): TokenVotingMember { - let id = user - .toHexString() - .concat('_') - .concat(pluginId); - let member = TokenVotingMember.load(id); if (!member) { - member = new TokenVotingMember(id); + createdNew = true; + member = new TokenVotingMember(memberEntityId); member.address = user; - member.balance = BigInt.zero(); + member.balance = getERC20Balance(user, tokenAddress); member.plugin = pluginId; - member.delegatee = id; // we assume by default member delegates itself - member.votingPower = BigInt.zero(); + member.delegatee = getDelegateeId(user, tokenAddress, pluginId); + member.votingPower = getVotingPower(user, tokenAddress); } - return member; + return new TokenVotingMemberResult(member, createdNew); } export function handleTransfer(event: Transfer): void { let context = dataSource.context(); let pluginId = context.getString('pluginId'); + let tokenAddress = event.address; if (event.params.from != Address.zero()) { - let fromMember = getOrCreateMember(event.params.from, pluginId); - fromMember.balance = fromMember.balance.minus(event.params.value); + let result = getOrCreateMember(event.params.from, pluginId, tokenAddress); + let fromMember = result.entity; + + // in the case of an existing member, update the balance + if (!result.createdNew) { + fromMember.balance = fromMember.balance.minus(event.params.value); + } fromMember.save(); } if (event.params.to != Address.zero()) { - let toMember = getOrCreateMember(event.params.to, pluginId); - toMember.balance = toMember.balance.plus(event.params.value); + let result = getOrCreateMember(event.params.to, pluginId, tokenAddress); + let toMember = result.entity; + + // in the case of an existing member, update the balance + if (!result.createdNew) { + toMember.balance = toMember.balance.plus(event.params.value); + } toMember.save(); } } @@ -46,48 +71,87 @@ export function handleTransfer(event: Transfer): void { export function handleDelegateChanged(event: DelegateChanged): void { let context = dataSource.context(); let pluginId = context.getString('pluginId'); + let tokenAddress = event.address; + + const toDelegate = event.params.toDelegate; // make sure `fromDelegate` & `toDelegate`are members if (event.params.fromDelegate != Address.zero()) { - let fromMember = getOrCreateMember(event.params.fromDelegate, pluginId); - fromMember.save(); - } - if (event.params.toDelegate != Address.zero()) { - let toMember = getOrCreateMember(event.params.toDelegate, pluginId); - toMember.save(); + let resultFromDelegate = getOrCreateMember( + event.params.fromDelegate, + pluginId, + tokenAddress + ); + resultFromDelegate.entity.save(); } // make sure `delegator` is member and set delegatee if (event.params.delegator != Address.zero()) { - let delegator = getOrCreateMember(event.params.delegator, pluginId); + let resultDelegator = getOrCreateMember( + event.params.delegator, + pluginId, + tokenAddress + ); + let delegator = resultDelegator.entity; // set delegatee - let delegatee = event.params.toDelegate - .toHexString() - .concat('_') - .concat(pluginId); - - delegator.delegatee = delegatee; - + if (toDelegate != Address.zero()) { + const resultDelegatee = getOrCreateMember( + toDelegate, + pluginId, + tokenAddress + ); + const delegatee = resultDelegatee.entity; + const delegateeId = generateMemberEntityId( + Address.fromString(pluginId), + Address.fromBytes(delegatee.address) + ); + delegatee.save(); + delegator.delegatee = delegateeId; + } delegator.save(); } } export function handleDelegateVotesChanged(event: DelegateVotesChanged): void { - let context = dataSource.context(); - let pluginId = context.getString('pluginId'); + const delegate = event.params.delegate; + if (delegate == Address.zero()) return; + const newVotingPower = event.params.newBalance; + + const context = dataSource.context(); + const pluginId = context.getString('pluginId'); + const tokenAddress = event.address; - if (event.params.delegate != Address.zero()) { - let member = getOrCreateMember(event.params.delegate, pluginId); - if ( - member.balance.equals(BigInt.zero()) && - event.params.newBalance.equals(BigInt.zero()) - ) { + let result = getOrCreateMember(delegate, pluginId, tokenAddress); + let member = result.entity; + + if (isZeroBalanceAndVotingPower(member.balance, newVotingPower)) { + if (shouldRemoveMember(event.address, delegate)) { store.remove('TokenVotingMember', member.id); - } else { - // Assign the cumulative delegated votes to this member from all their delegators. - member.votingPower = event.params.newBalance; - member.save(); + return; } } + member.votingPower = newVotingPower; + member.save(); +} + +function isZeroBalanceAndVotingPower( + memberBalance: BigInt, + votingPower: BigInt +): boolean { + return ( + memberBalance.equals(BigInt.zero()) && votingPower.equals(BigInt.zero()) + ); +} + +function shouldRemoveMember( + contractAddress: Address, + delegate: Address +): boolean { + const governanceERC20Contract = GovernanceERC20Contract.bind(contractAddress); + const delegates = governanceERC20Contract.try_delegates(delegate); + if (!delegates.reverted) { + return delegates.value == delegate || delegates.value == Address.zero(); + } + return false; } diff --git a/packages/subgraph/src/packages/token/token-voting.ts b/packages/subgraph/src/packages/token/token-voting.ts index a8456979f..57c9189cc 100644 --- a/packages/subgraph/src/packages/token/token-voting.ts +++ b/packages/subgraph/src/packages/token/token-voting.ts @@ -1,31 +1,28 @@ -import {BigInt, dataSource, DataSourceContext} from '@graphprotocol/graph-ts'; - +import { + Action, + TokenVotingPlugin, + TokenVotingProposal, + TokenVotingVoter, + TokenVotingVote, +} from '../../../generated/schema'; +import {GovernanceERC20} from '../../../generated/templates'; import { VoteCast, ProposalCreated, ProposalExecuted, VotingSettingsUpdated, MembershipContractAnnounced, - TokenVoting + TokenVoting, } from '../../../generated/templates/TokenVoting/TokenVoting'; - -import {GovernanceERC20} from '../../../generated/templates'; - -import { - Action, - TokenVotingPlugin, - TokenVotingProposal, - TokenVotingVoter, - TokenVotingVote -} from '../../../generated/schema'; - import {RATIO_BASE, VOTER_OPTIONS, VOTING_MODES} from '../../utils/constants'; +import {generateMemberEntityId, generateVoteEntityId} from '../../utils/ids'; +import {identifyAndFetchOrCreateERC20TokenEntity} from '../../utils/tokens/erc20'; import { - fetchERC20, - fetchWrappedERC20, - supportsERC20Wrapped -} from '../../utils/tokens/erc20'; -import {getProposalId} from '../../utils/proposals'; + generateActionEntityId, + generatePluginEntityId, + generateProposalEntityId, +} from '@aragon/osx-commons-subgraph'; +import {BigInt, dataSource, DataSourceContext} from '@graphprotocol/graph-ts'; export function handleProposalCreated(event: ProposalCreated): void { let context = dataSource.context(); @@ -41,21 +38,25 @@ export function _handleProposalCreated( metadata: string ): void { let pluginAddress = event.address; + let pluginEntityId = generatePluginEntityId(pluginAddress); let pluginProposalId = event.params.proposalId; - let proposalId = getProposalId(pluginAddress, pluginProposalId); + let proposalEntityId = generateProposalEntityId( + pluginAddress, + pluginProposalId + ); - let proposalEntity = new TokenVotingProposal(proposalId); + let proposalEntity = new TokenVotingProposal(proposalEntityId); proposalEntity.dao = daoId; - proposalEntity.plugin = pluginAddress.toHexString(); + proposalEntity.plugin = pluginEntityId; proposalEntity.pluginProposalId = pluginProposalId; proposalEntity.creator = event.params.creator; proposalEntity.metadata = metadata; proposalEntity.createdAt = event.block.timestamp; proposalEntity.creationBlockNumber = event.block.number; proposalEntity.allowFailureMap = event.params.allowFailureMap; - proposalEntity.potentiallyExecutable = false; + proposalEntity.approvalReached = false; - let contract = TokenVoting.bind(event.address); + let contract = TokenVoting.bind(pluginAddress); let proposal = contract.try_getProposal(pluginProposalId); if (!proposal.reverted) { @@ -85,18 +86,17 @@ export function _handleProposalCreated( for (let index = 0; index < actions.length; index++) { const action = actions[index]; - let actionId = getProposalId(event.address, pluginProposalId) - .concat('_') - .concat(index.toString()); + let actionId = generateActionEntityId(proposalEntityId, index); let actionEntity = new Action(actionId); actionEntity.to = action.to; actionEntity.value = action.value; actionEntity.data = action.data; actionEntity.dao = daoId; - actionEntity.proposal = proposalId; + actionEntity.proposal = proposalEntityId; actionEntity.save(); } + proposalEntity.isSignaling = actions.length == 0; // totalVotingPower proposalEntity.totalVotingPower = contract.try_totalVotingPower( @@ -107,7 +107,7 @@ export function _handleProposalCreated( proposalEntity.save(); // update vote length - let packageEntity = TokenVotingPlugin.load(event.address.toHexString()); + let packageEntity = TokenVotingPlugin.load(pluginEntityId); if (packageEntity) { let voteLength = contract.try_proposalCount(); if (!voteLength.reverted) { @@ -118,26 +118,30 @@ export function _handleProposalCreated( } export function handleVoteCast(event: VoteCast): void { - let pluginId = event.address.toHexString(); - let voter = event.params.voter.toHexString(); - let voterId = pluginId.concat('_').concat(voter); + let pluginAddress = event.address; + let voterAddress = event.params.voter; + let voterEntityId = generateMemberEntityId(pluginAddress, voterAddress); let pluginProposalId = event.params.proposalId; - let proposalId = getProposalId(event.address, pluginProposalId); - let voterVoteId = voter.concat('_').concat(proposalId); + let proposalEntityId = generateProposalEntityId( + pluginAddress, + pluginProposalId + ); + let pluginEntityId = generatePluginEntityId(pluginAddress); + let voterVoteEntityId = generateVoteEntityId(voterAddress, proposalEntityId); let voteOption = VOTER_OPTIONS.get(event.params.voteOption); if (voteOption === 'None') { return; } - let voterProposalVoteEntity = TokenVotingVote.load(voterVoteId); + let voterProposalVoteEntity = TokenVotingVote.load(voterVoteEntityId); if (voterProposalVoteEntity) { voterProposalVoteEntity.voteReplaced = true; voterProposalVoteEntity.updatedAt = event.block.timestamp; } else { - voterProposalVoteEntity = new TokenVotingVote(voterVoteId); - voterProposalVoteEntity.voter = voterId; - voterProposalVoteEntity.proposal = proposalId; + voterProposalVoteEntity = new TokenVotingVote(voterVoteEntityId); + voterProposalVoteEntity.voter = voterEntityId; + voterProposalVoteEntity.proposal = proposalEntityId; voterProposalVoteEntity.createdAt = event.block.timestamp; voterProposalVoteEntity.voteReplaced = false; voterProposalVoteEntity.updatedAt = BigInt.zero(); @@ -147,11 +151,11 @@ export function handleVoteCast(event: VoteCast): void { voterProposalVoteEntity.save(); // voter - let voterEntity = TokenVotingVoter.load(voterId); + let voterEntity = TokenVotingVoter.load(voterEntityId); if (!voterEntity) { - voterEntity = new TokenVotingVoter(voterId); - voterEntity.address = voter; - voterEntity.plugin = pluginId; + voterEntity = new TokenVotingVoter(voterEntityId); + voterEntity.address = voterEntityId; + voterEntity.plugin = pluginEntityId; voterEntity.lastUpdated = event.block.timestamp; voterEntity.save(); } else { @@ -160,9 +164,9 @@ export function handleVoteCast(event: VoteCast): void { } // update count - let proposalEntity = TokenVotingProposal.load(proposalId); + let proposalEntity = TokenVotingProposal.load(proposalEntityId); if (proposalEntity) { - let contract = TokenVoting.bind(event.address); + let contract = TokenVoting.bind(pluginAddress); let proposal = contract.try_getProposal(pluginProposalId); if (!proposal.reverted) { @@ -206,7 +210,7 @@ export function handleVoteCast(event: VoteCast): void { let minParticipationReached = castedVotingPower.ge(minVotingPower); // Used when proposal has ended. - proposalEntity.potentiallyExecutable = + proposalEntity.approvalReached = supportThresholdReached && minParticipationReached; // Used when proposal has not ended. @@ -222,12 +226,15 @@ export function handleVoteCast(event: VoteCast): void { export function handleProposalExecuted(event: ProposalExecuted): void { let pluginProposalId = event.params.proposalId; - let proposalId = getProposalId(event.address, pluginProposalId); + let proposalEntityId = generateProposalEntityId( + event.address, + pluginProposalId + ); - let proposalEntity = TokenVotingProposal.load(proposalId); + let proposalEntity = TokenVotingProposal.load(proposalEntityId); if (proposalEntity) { proposalEntity.executed = true; - proposalEntity.potentiallyExecutable = false; + proposalEntity.approvalReached = false; proposalEntity.executionDate = event.block.timestamp; proposalEntity.executionBlockNumber = event.block.number; proposalEntity.executionTxHash = event.transaction.hash; @@ -238,7 +245,9 @@ export function handleProposalExecuted(event: ProposalExecuted): void { export function handleVotingSettingsUpdated( event: VotingSettingsUpdated ): void { - let packageEntity = TokenVotingPlugin.load(event.address.toHexString()); + let packageEntity = TokenVotingPlugin.load( + generatePluginEntityId(event.address) + ); if (packageEntity) { packageEntity.votingMode = VOTING_MODES.get(event.params.votingMode); packageEntity.supportThreshold = event.params.supportThreshold; @@ -253,32 +262,21 @@ export function handleMembershipContractAnnounced( event: MembershipContractAnnounced ): void { let token = event.params.definingContract; - let packageEntity = TokenVotingPlugin.load(event.address.toHexString()); + let pluginEntityId = generatePluginEntityId(event.address); + let packageEntity = TokenVotingPlugin.load(pluginEntityId); if (packageEntity) { - let contractAddress: string; - - if (supportsERC20Wrapped(token)) { - let contract = fetchWrappedERC20(token); - if (!contract) { - return; - } - contractAddress = contract.id; - } else { - let contract = fetchERC20(token); - if (!contract) { - return; - } - contractAddress = contract.id; + let tokenAddress = identifyAndFetchOrCreateERC20TokenEntity(token); + if (!tokenAddress) { + return; } - packageEntity.token = contractAddress; - + packageEntity.token = tokenAddress as string; packageEntity.save(); // Both GovernanceWrappedERC20/GovernanceERC20 use the `Transfer` event, so // It's safe to create the same type of template for them. let context = new DataSourceContext(); - context.setString('pluginId', event.address.toHexString()); + context.setString('pluginId', pluginEntityId); GovernanceERC20.createWithContext(event.params.definingContract, context); } } diff --git a/packages/subgraph/src/packages/token/utils.ts b/packages/subgraph/src/packages/token/utils.ts new file mode 100644 index 000000000..d20033fe2 --- /dev/null +++ b/packages/subgraph/src/packages/token/utils.ts @@ -0,0 +1,60 @@ +import {TokenVotingMember} from '../../../generated/schema'; +import {GovernanceERC20 as GovernanceERC20Contract} from '../../../generated/templates/GovernanceERC20/GovernanceERC20'; +import {ADDRESS_ZERO} from '../../utils/constants'; +import {generateMemberEntityId} from '../../utils/ids'; +import {Address, BigInt} from '@graphprotocol/graph-ts'; + +export function getERC20Balance(user: Address, tokenAddress: Address): BigInt { + let contract = GovernanceERC20Contract.bind(tokenAddress); + let balance = contract.balanceOf(user); + return balance; +} + +export function getDelegation( + user: Address, + tokenAddress: Address +): string | null { + let contract = GovernanceERC20Contract.bind(tokenAddress); + let delegate = contract.delegates(user); + + return delegate === Address.fromString(ADDRESS_ZERO) + ? null + : delegate.toHexString(); +} + +export function getDelegateeId( + user: Address, + tokenAddress: Address, + pluginId: string +): string | null { + let delegatee = getDelegation(user, tokenAddress); + return delegatee + ? generateMemberEntityId( + Address.fromString(pluginId), + Address.fromString(user.toHexString()) + ) + : null; +} + +export function getVotingPower(user: Address, tokenAddress: Address): BigInt { + let contract = GovernanceERC20Contract.bind(tokenAddress); + let votingPower = contract.getVotes(user); + return votingPower; +} + +/** + * A container for the result of the `getOrCreateMember` function. + * @param entity - The `TokenVotingMember` entity. + * @param createdNew - A boolean indicating whether the entity was created new + * or if false it was previously created. If the entity was created new, it already + * has the latest balance of the user, so no need to then update the balance. + */ +export class TokenVotingMemberResult { + entity: TokenVotingMember; + createdNew: boolean; + + constructor(entity: TokenVotingMember, createNew: boolean) { + this.entity = entity; + this.createdNew = createNew; + } +} diff --git a/packages/subgraph/src/plugin/pluginRepo.ts b/packages/subgraph/src/plugin/pluginRepo.ts index 170bf6352..f0997f61e 100644 --- a/packages/subgraph/src/plugin/pluginRepo.ts +++ b/packages/subgraph/src/plugin/pluginRepo.ts @@ -1,17 +1,27 @@ -import { - ReleaseMetadataUpdated, - VersionCreated -} from '../../generated/templates/PluginRepoTemplate/PluginRepo'; import { PluginVersion, PluginSetup, - PluginRelease + PluginRelease, + Permission, } from '../../generated/schema'; -import {getPluginVersionId} from './utils'; +import { + ReleaseMetadataUpdated, + VersionCreated, + Granted, + Revoked, +} from '../../generated/templates/PluginRepoTemplate/PluginRepo'; +import { + generatePermissionEntityId, + generatePluginReleaseEntityId, + generatePluginRepoEntityId, + generatePluginSetupEntityId, + generatePluginVersionEntityId, +} from '@aragon/osx-commons-subgraph'; +import {store} from '@graphprotocol/graph-ts'; export function handleVersionCreated(event: VersionCreated): void { // PluginSetup - let pluginSetupId = event.params.pluginSetup.toHexString(); + let pluginSetupId = generatePluginSetupEntityId(event.params.pluginSetup); let pluginSetupEntity = PluginSetup.load(pluginSetupId); if (!pluginSetupEntity) { @@ -20,23 +30,25 @@ export function handleVersionCreated(event: VersionCreated): void { } // PluginVersion - let pluginRepoId = event.address.toHexString(); - let pluginReleaseId = pluginRepoId - .concat('_') - .concat(event.params.release.toString()); - - let pluginVersionId = getPluginVersionId( - pluginRepoId, - event.params.release, - event.params.build + const pluginRepoAddress = event.address; + const build = event.params.build; + const release = event.params.release; + let pluginReleaseId = generatePluginReleaseEntityId( + pluginRepoAddress, + release + ); + let pluginVersionId = generatePluginVersionEntityId( + pluginRepoAddress, + release, + build ); let entity = new PluginVersion(pluginVersionId); - entity.pluginRepo = event.address.toHexString(); + entity.pluginRepo = pluginRepoAddress.toHexString(); entity.pluginSetup = pluginSetupId; entity.release = pluginReleaseId; - entity.build = event.params.build; + entity.build = build; entity.metadata = event.params.buildMetadata.toString(); entity.save(); @@ -45,13 +57,15 @@ export function handleVersionCreated(event: VersionCreated): void { export function handleReleaseMetadataUpdated( event: ReleaseMetadataUpdated ): void { - let pluginRepoId = event.address.toHexString(); + let pluginRepoAddress = event.address; + let pluginRepoId = generatePluginRepoEntityId(pluginRepoAddress); let pluginRelease = event.params.release; let releaseMetadata = event.params.releaseMetadata.toString(); - let pluginReleaseEntityId = pluginRepoId - .concat('_') - .concat(pluginRelease.toString()); + let pluginReleaseEntityId = generatePluginReleaseEntityId( + pluginRepoAddress, + pluginRelease + ); let pluginReleaseEntity = PluginRelease.load(pluginReleaseEntityId); if (!pluginReleaseEntity) { @@ -63,3 +77,52 @@ export function handleReleaseMetadataUpdated( pluginReleaseEntity.metadata = releaseMetadata; pluginReleaseEntity.save(); } + +export function handleGranted(event: Granted): void { + const contractAddress = event.address; + const where = event.params.where; + const permissionId = event.params.permissionId; + const who = event.params.who; + + const permissionEntityId = generatePermissionEntityId( + contractAddress, + permissionId, + where, + who + ); + + // Permission + let permissionEntity = Permission.load(permissionEntityId); + if (!permissionEntity) { + permissionEntity = new Permission(permissionEntityId); + permissionEntity.where = where; + permissionEntity.permissionId = permissionId; + permissionEntity.who = who; + permissionEntity.actor = event.params.here; + permissionEntity.condition = event.params.condition; + + permissionEntity.pluginRepo = contractAddress.toHexString(); + + permissionEntity.save(); + } +} + +export function handleRevoked(event: Revoked): void { + // permission + const contractAddress = event.address; + const where = event.params.where; + const permissionId = event.params.permissionId; + const who = event.params.who; + + const permissionEntityId = generatePermissionEntityId( + contractAddress, + permissionId, + where, + who + ); + + const permissionEntity = Permission.load(permissionEntityId); + if (permissionEntity) { + store.remove('Permission', permissionEntityId); + } +} diff --git a/packages/subgraph/src/plugin/pluginSetupProcessor.ts b/packages/subgraph/src/plugin/pluginSetupProcessor.ts index 45cb19035..f6eb236bb 100644 --- a/packages/subgraph/src/plugin/pluginSetupProcessor.ts +++ b/packages/subgraph/src/plugin/pluginSetupProcessor.ts @@ -1,42 +1,56 @@ -import {Bytes, log} from '@graphprotocol/graph-ts'; import { InstallationApplied, InstallationPrepared, UninstallationApplied, UninstallationPrepared, UpdateApplied, - UpdatePrepared + UpdatePrepared, } from '../../generated/PluginSetupProcessor/PluginSetupProcessor'; import { PluginInstallation, PluginPermission, - PluginPreparation + PluginPreparation, } from '../../generated/schema'; +import {addPlugin, PERMISSION_OPERATIONS} from './utils'; import { - addPlugin, - getPluginInstallationId, - getPluginVersionId, - PERMISSION_OPERATIONS -} from './utils'; + generateDaoEntityId, + generatePluginEntityId, + generatePluginInstallationEntityId, + generatePluginPermissionEntityId, + generatePluginPreparationEntityId, + generatePluginRepoEntityId, + generatePluginVersionEntityId, +} from '@aragon/osx-commons-subgraph'; +import {Bytes, log} from '@graphprotocol/graph-ts'; export function handleInstallationPrepared(event: InstallationPrepared): void { - let dao = event.params.dao.toHexString(); - let plugin = event.params.plugin.toHexString(); - let setupId = event.params.preparedSetupId.toHexString(); - let pluginRepo = event.params.pluginSetupRepo.toHexString(); - let pluginVersionId = getPluginVersionId( - pluginRepo, + let daoAddress = event.params.dao; + let pluginAddress = event.params.plugin; + let pluginRepoAddress = event.params.pluginSetupRepo; + let daoEntityId = generateDaoEntityId(daoAddress); + let pluginEntityId = generatePluginEntityId(pluginAddress); + let pluginRepoEntityId = generatePluginRepoEntityId(pluginRepoAddress); + let preparedSetupId = event.params.preparedSetupId; + + let pluginVersionId = generatePluginVersionEntityId( + pluginRepoAddress, event.params.versionTag.release, event.params.versionTag.build ); - let installationId = getPluginInstallationId(dao, plugin); + let installationId = generatePluginInstallationEntityId( + daoAddress, + pluginAddress + ); if (!installationId) { - log.error('Failed to get installationId', [dao, plugin]); + log.error('Failed to get installationId', [daoEntityId, pluginEntityId]); return; } - - let preparationId = `${installationId.toHexString()}_${setupId}`; + installationId = installationId as string; + let preparationId = generatePluginPreparationEntityId( + installationId, + preparedSetupId + ); let helpers: Bytes[] = []; for (let i = 0; i < event.params.preparedSetupData.helpers.length; i++) { @@ -44,11 +58,11 @@ export function handleInstallationPrepared(event: InstallationPrepared): void { } let preparationEntity = new PluginPreparation(preparationId); - preparationEntity.installation = installationId.toHexString(); + preparationEntity.installation = installationId; preparationEntity.creator = event.params.sender; - preparationEntity.dao = dao; + preparationEntity.dao = daoEntityId; preparationEntity.preparedSetupId = event.params.preparedSetupId; - preparationEntity.pluginRepo = event.params.pluginSetupRepo.toHexString(); + preparationEntity.pluginRepo = pluginRepoEntityId; preparationEntity.pluginVersion = pluginVersionId; preparationEntity.data = event.params.data; preparationEntity.pluginAddress = event.params.plugin; @@ -58,8 +72,14 @@ export function handleInstallationPrepared(event: InstallationPrepared): void { for (let i = 0; i < event.params.preparedSetupData.permissions.length; i++) { let permission = event.params.preparedSetupData.permissions[i]; + let permissionId = generatePluginPermissionEntityId( + preparationId, + permission.operation, + permission.where, + permission.who, + permission.permissionId + ); let operation = PERMISSION_OPERATIONS.get(permission.operation); - let permissionId = `${preparationId}_${operation}_${permission.where.toHexString()}_${permission.who.toHexString()}_${permission.permissionId.toHexString()}`; let permissionEntity = new PluginPermission(permissionId); permissionEntity.pluginPreparation = preparationId; permissionEntity.operation = operation; @@ -72,31 +92,41 @@ export function handleInstallationPrepared(event: InstallationPrepared): void { permissionEntity.save(); } - let pluginEntity = PluginInstallation.load(installationId.toHexString()); + let pluginEntity = PluginInstallation.load(installationId); if (!pluginEntity) { - pluginEntity = new PluginInstallation(installationId.toHexString()); + pluginEntity = new PluginInstallation(installationId); } pluginEntity.state = 'InstallationPrepared'; - pluginEntity.dao = dao; + pluginEntity.dao = daoEntityId; pluginEntity.save(); - addPlugin(dao, event.params.plugin); + addPlugin(daoEntityId, event.params.plugin); } export function handleInstallationApplied(event: InstallationApplied): void { - let dao = event.params.dao.toHexString(); - let plugin = event.params.plugin.toHexString(); - let installationId = getPluginInstallationId(dao, plugin); + let daoAddress = event.params.dao; + let pluginAddress = event.params.plugin; + let daoEntityId = generateDaoEntityId(daoAddress); + let pluginEntityId = generatePluginEntityId(pluginAddress); + let installationId = generatePluginInstallationEntityId( + daoAddress, + pluginAddress + ); if (!installationId) { - log.error('Failed to get installationId', [dao, plugin]); + log.error('Failed to get installationId', [daoEntityId, pluginEntityId]); return; } - let preparationId = `${installationId.toHexString()}_${event.params.preparedSetupId.toHexString()}`; - let pluginEntity = PluginInstallation.load(installationId.toHexString()); + installationId = installationId as string; + let preparationId = generatePluginPreparationEntityId( + installationId, + event.params.preparedSetupId + ); + + let pluginEntity = PluginInstallation.load(installationId); if (!pluginEntity) { - pluginEntity = new PluginInstallation(installationId.toHexString()); - pluginEntity.dao = dao; + pluginEntity = new PluginInstallation(installationId); + pluginEntity.dao = daoEntityId; } let pluginPreparationEntity = PluginPreparation.load(preparationId); @@ -104,7 +134,7 @@ export function handleInstallationApplied(event: InstallationApplied): void { pluginEntity.appliedPluginRepo = pluginPreparationEntity.pluginRepo; pluginEntity.appliedVersion = pluginPreparationEntity.pluginVersion; } - pluginEntity.plugin = plugin; + pluginEntity.plugin = pluginEntityId; pluginEntity.appliedPreparation = preparationId; pluginEntity.appliedSetupId = event.params.appliedSetupId; pluginEntity.state = 'Installed'; @@ -112,24 +142,33 @@ export function handleInstallationApplied(event: InstallationApplied): void { } export function handleUpdatePrepared(event: UpdatePrepared): void { - let dao = event.params.dao.toHexString(); - let plugin = event.params.setupPayload.plugin.toHexString(); - let setupId = event.params.preparedSetupId.toHexString(); - let pluginRepo = event.params.pluginSetupRepo.toHexString(); - - let pluginVersionId = getPluginVersionId( - pluginRepo, + let daoAddress = event.params.dao; + let pluginAddress = event.params.setupPayload.plugin; + let pluginRepoAddress = event.params.pluginSetupRepo; + let setupId = event.params.preparedSetupId; + let daoEntityId = generateDaoEntityId(daoAddress); + let pluginEntityId = generatePluginEntityId(pluginAddress); + let pluginVersionId = generatePluginVersionEntityId( + pluginRepoAddress, event.params.versionTag.release, event.params.versionTag.build ); - let installationId = getPluginInstallationId(dao, plugin); + let installationId = generatePluginInstallationEntityId( + daoAddress, + pluginAddress + ); if (!installationId) { - log.error('Failed to get installationId', [dao, plugin]); + log.error('Failed to get installationId', [daoEntityId, pluginEntityId]); return; } - let preparationId = `${installationId.toHexString()}_${setupId}`; + installationId = installationId as string; + + let preparationId = generatePluginPreparationEntityId( + installationId, + setupId + ); let helpers: Bytes[] = []; for (let i = 0; i < event.params.preparedSetupData.helpers.length; i++) { @@ -137,11 +176,13 @@ export function handleUpdatePrepared(event: UpdatePrepared): void { } let preparationEntity = new PluginPreparation(preparationId); - preparationEntity.installation = installationId.toHexString(); + preparationEntity.installation = installationId; preparationEntity.creator = event.params.sender; - preparationEntity.dao = dao; + preparationEntity.dao = daoEntityId; preparationEntity.preparedSetupId = event.params.preparedSetupId; - preparationEntity.pluginRepo = event.params.pluginSetupRepo.toHexString(); + preparationEntity.pluginRepo = generatePluginEntityId( + event.params.pluginSetupRepo + ); preparationEntity.pluginVersion = pluginVersionId; preparationEntity.data = event.params.initData; preparationEntity.pluginAddress = event.params.setupPayload.plugin; @@ -152,7 +193,13 @@ export function handleUpdatePrepared(event: UpdatePrepared): void { for (let i = 0; i < event.params.preparedSetupData.permissions.length; i++) { let permission = event.params.preparedSetupData.permissions[i]; let operation = PERMISSION_OPERATIONS.get(permission.operation); - let permissionId = `${preparationId}_${operation}_${permission.where.toHexString()}_${permission.who.toHexString()}_${permission.permissionId.toHexString()}`; + let permissionId = generatePluginPermissionEntityId( + preparationId, + permission.operation, + permission.where, + permission.who, + permission.permissionId + ); let permissionEntity = new PluginPermission(permissionId); permissionEntity.pluginPreparation = preparationId; permissionEntity.operation = operation; @@ -165,10 +212,10 @@ export function handleUpdatePrepared(event: UpdatePrepared): void { permissionEntity.save(); } - let pluginEntity = PluginInstallation.load(installationId.toHexString()); + let pluginEntity = PluginInstallation.load(installationId); if (!pluginEntity) { - pluginEntity = new PluginInstallation(installationId.toHexString()); - pluginEntity.dao = dao; + pluginEntity = new PluginInstallation(installationId); + pluginEntity.dao = daoEntityId; } pluginEntity.state = 'UpdatePrepared'; @@ -176,19 +223,28 @@ export function handleUpdatePrepared(event: UpdatePrepared): void { } export function handleUpdateApplied(event: UpdateApplied): void { - let dao = event.params.dao.toHexString(); - let plugin = event.params.plugin.toHexString(); - let installationId = getPluginInstallationId(dao, plugin); + let daoAddress = event.params.dao; + let pluginAddress = event.params.plugin; + let daoEntityId = generateDaoEntityId(daoAddress); + let pluginEntityId = generatePluginEntityId(pluginAddress); + let installationId = generatePluginInstallationEntityId( + daoAddress, + pluginAddress + ); if (!installationId) { - log.error('Failed to get installationId', [dao, plugin]); + log.error('Failed to get installationId', [daoEntityId, pluginEntityId]); return; } - let preparationId = `${installationId.toHexString()}_${event.params.preparedSetupId.toHexString()}`; + installationId = installationId as string; + let preparationId = generatePluginPreparationEntityId( + installationId, + event.params.preparedSetupId + ); - let pluginEntity = PluginInstallation.load(installationId.toHexString()); + let pluginEntity = PluginInstallation.load(installationId); if (!pluginEntity) { - pluginEntity = new PluginInstallation(installationId.toHexString()); - pluginEntity.dao = dao; + pluginEntity = new PluginInstallation(installationId); + pluginEntity.dao = daoEntityId; } let pluginPreparationEntity = PluginPreparation.load(preparationId); @@ -196,43 +252,53 @@ export function handleUpdateApplied(event: UpdateApplied): void { pluginEntity.appliedPluginRepo = pluginPreparationEntity.pluginRepo; pluginEntity.appliedVersion = pluginPreparationEntity.pluginVersion; } - pluginEntity.plugin = plugin; + pluginEntity.plugin = pluginEntityId; pluginEntity.appliedPreparation = preparationId; pluginEntity.appliedSetupId = event.params.appliedSetupId; pluginEntity.state = 'Installed'; pluginEntity.save(); - addPlugin(dao, event.params.plugin); + addPlugin(daoEntityId, pluginAddress); } export function handleUninstallationPrepared( event: UninstallationPrepared ): void { - let dao = event.params.dao.toHexString(); - let plugin = event.params.setupPayload.plugin.toHexString(); - let setupId = event.params.preparedSetupId.toHexString(); - let pluginRepo = event.params.pluginSetupRepo.toHexString(); - - let pluginVersionId = getPluginVersionId( - pluginRepo, + let daoAddress = event.params.dao; + let pluginAddress = event.params.setupPayload.plugin; + let setupId = event.params.preparedSetupId; + let pluginRepoAddress = event.params.pluginSetupRepo; + let daoEntityId = generateDaoEntityId(daoAddress); + let pluginEntityId = generatePluginEntityId(pluginAddress); + let pluginRepoEntityId = generatePluginRepoEntityId(pluginRepoAddress); + + let pluginVersionId = generatePluginVersionEntityId( + pluginRepoAddress, event.params.versionTag.release, event.params.versionTag.build ); - let installationId = getPluginInstallationId(dao, plugin); + let installationId = generatePluginInstallationEntityId( + daoAddress, + pluginAddress + ); if (!installationId) { - log.error('Failed to get installationId', [dao, plugin]); + log.error('Failed to get installationId', [daoEntityId, pluginEntityId]); return; } + installationId = installationId as string; - let preparationId = `${installationId.toHexString()}_${setupId}`; + let preparationId = generatePluginPreparationEntityId( + installationId, + setupId + ); let preparationEntity = new PluginPreparation(preparationId); - preparationEntity.installation = installationId.toHexString(); + preparationEntity.installation = installationId; preparationEntity.creator = event.params.sender; - preparationEntity.dao = dao; + preparationEntity.dao = daoEntityId; preparationEntity.preparedSetupId = event.params.preparedSetupId; - preparationEntity.pluginRepo = event.params.pluginSetupRepo.toHexString(); + preparationEntity.pluginRepo = pluginRepoEntityId; preparationEntity.pluginVersion = pluginVersionId; preparationEntity.pluginAddress = event.params.setupPayload.plugin; preparationEntity.helpers = []; @@ -242,7 +308,13 @@ export function handleUninstallationPrepared( for (let i = 0; i < event.params.permissions.length; i++) { let permission = event.params.permissions[i]; let operation = PERMISSION_OPERATIONS.get(permission.operation); - let permissionId = `${preparationId}_${operation}_${permission.where.toHexString()}_${permission.who.toHexString()}_${permission.permissionId.toHexString()}`; + let permissionId = generatePluginPermissionEntityId( + preparationId, + permission.operation, + permission.where, + permission.who, + permission.permissionId + ); let permissionEntity = new PluginPermission(permissionId); permissionEntity.pluginPreparation = preparationId; permissionEntity.operation = operation; @@ -255,10 +327,10 @@ export function handleUninstallationPrepared( permissionEntity.save(); } - let pluginEntity = PluginInstallation.load(installationId.toHexString()); + let pluginEntity = PluginInstallation.load(installationId); if (!pluginEntity) { - pluginEntity = new PluginInstallation(installationId.toHexString()); - pluginEntity.dao = dao; + pluginEntity = new PluginInstallation(installationId); + pluginEntity.dao = daoEntityId; } pluginEntity.state = 'UninstallPrepared'; pluginEntity.save(); @@ -267,19 +339,30 @@ export function handleUninstallationPrepared( export function handleUninstallationApplied( event: UninstallationApplied ): void { - let dao = event.params.dao.toHexString(); - let plugin = event.params.plugin.toHexString(); - let installationId = getPluginInstallationId(dao, plugin); + let daoAddress = event.params.dao; + let pluginAddress = event.params.plugin; + let daoEntityId = generateDaoEntityId(daoAddress); + let pluginEntityId = generatePluginEntityId(pluginAddress); + let installationId = generatePluginInstallationEntityId( + daoAddress, + pluginAddress + ); if (!installationId) { - log.error('Failed to get installationId', [dao, plugin]); + log.error('Failed to get installationId', [daoEntityId, pluginEntityId]); return; } - let preparationId = `${installationId.toHexString()}_${event.params.preparedSetupId.toHexString()}`; - let pluginEntity = PluginInstallation.load(installationId.toHexString()); + installationId = installationId as string; + + let preparationId = generatePluginPreparationEntityId( + installationId, + event.params.preparedSetupId + ); + + let pluginEntity = PluginInstallation.load(installationId); if (!pluginEntity) { - pluginEntity = new PluginInstallation(installationId.toHexString()); - pluginEntity.dao = dao; + pluginEntity = new PluginInstallation(installationId); + pluginEntity.dao = daoEntityId; } pluginEntity.appliedPreparation = preparationId; pluginEntity.state = 'Uninstalled'; diff --git a/packages/subgraph/src/plugin/utils.ts b/packages/subgraph/src/plugin/utils.ts index a2447dae3..8fabfc118 100644 --- a/packages/subgraph/src/plugin/utils.ts +++ b/packages/subgraph/src/plugin/utils.ts @@ -1,41 +1,41 @@ -import { - Address, - Bytes, - DataSourceContext, - ethereum, - crypto, - ByteArray, - BigInt -} from '@graphprotocol/graph-ts'; - -import {TokenVoting as TokenVotingContract} from '../../generated/templates/TokenVoting/TokenVoting'; -import {AddresslistVoting as AddresslistVotingContract} from '../../generated/templates/AddresslistVoting/AddresslistVoting'; import {ERC165 as ERC165Contract} from '../../generated/PluginSetupProcessor/ERC165'; -import { - TokenVoting, - AddresslistVoting, - Admin, - Multisig -} from '../../generated/templates'; import { TokenVotingPlugin, AddresslistVotingPlugin, AdminPlugin, - MultisigPlugin + MultisigPlugin, } from '../../generated/schema'; import { - TOKEN_VOTING_INTERFACE, - ADDRESSLIST_VOTING_INTERFACE, - ADMIN_INTERFACE, + TokenVoting, + AddresslistVoting, + Admin, + Multisig, +} from '../../generated/templates'; +import {AddresslistVoting as AddresslistVotingContract} from '../../generated/templates/AddresslistVoting/AddresslistVoting'; +import {TokenVoting as TokenVotingContract} from '../../generated/templates/TokenVoting/TokenVoting'; +import { VOTING_MODES, - MULTISIG_INTERFACE + ADMIN_INTERFACE_ID, + TOKEN_VOTING_INTERFACE_ID, + ADDRESSLIST_VOTING_INTERFACE_ID, + MULTISIG_INTERFACE_ID, } from '../utils/constants'; import {supportsInterface} from '../utils/erc165'; import { - fetchERC20, - fetchWrappedERC20, - supportsERC20Wrapped + fetchOrCreateERC20Entity, + fetchOrCreateWrappedERC20Entity, + supportsERC20Wrapped, } from '../utils/tokens/erc20'; +import {generatePluginEntityId} from '@aragon/osx-commons-subgraph'; +import { + Address, + Bytes, + DataSourceContext, + ethereum, + crypto, + ByteArray, + BigInt, +} from '@graphprotocol/graph-ts'; export const PERMISSION_OPERATIONS = new Map() .set(0, 'Grant') @@ -67,14 +67,14 @@ function createTokenVotingPlugin(plugin: Address, daoId: string): void { if (!token.reverted) { let tokenAddress = token.value; if (supportsERC20Wrapped(tokenAddress)) { - let contract = fetchWrappedERC20(tokenAddress); + let contract = fetchOrCreateWrappedERC20Entity(tokenAddress); if (!contract) { return; } packageEntity.token = contract.id; } else { - let contract = fetchERC20(tokenAddress); + let contract = fetchOrCreateERC20Entity(tokenAddress); if (!contract) { return; } @@ -93,9 +93,10 @@ function createTokenVotingPlugin(plugin: Address, daoId: string): void { } function createAddresslistVotingPlugin(plugin: Address, daoId: string): void { - let packageEntity = AddresslistVotingPlugin.load(plugin.toHexString()); + let pluginEntityId = generatePluginEntityId(plugin); + let packageEntity = AddresslistVotingPlugin.load(pluginEntityId); if (!packageEntity) { - packageEntity = new AddresslistVotingPlugin(plugin.toHexString()); + packageEntity = new AddresslistVotingPlugin(pluginEntityId); packageEntity.pluginAddress = plugin; packageEntity.dao = daoId; packageEntity.proposalCount = BigInt.zero(); @@ -173,16 +174,16 @@ export function addPlugin(daoId: string, plugin: Address): void { let tokenVotingInterfaceSupported = supportsInterface( contract, - TOKEN_VOTING_INTERFACE + TOKEN_VOTING_INTERFACE_ID ); let addresslistInterfaceSupported = supportsInterface( contract, - ADDRESSLIST_VOTING_INTERFACE + ADDRESSLIST_VOTING_INTERFACE_ID ); - let adminInterfaceSupported = supportsInterface(contract, ADMIN_INTERFACE); + let adminInterfaceSupported = supportsInterface(contract, ADMIN_INTERFACE_ID); let multisigInterfaceSupported = supportsInterface( contract, - MULTISIG_INTERFACE + MULTISIG_INTERFACE_ID ); if (tokenVotingInterfaceSupported) { diff --git a/packages/subgraph/src/registries/daoRegistry.ts b/packages/subgraph/src/registries/daoRegistry.ts index d5c993c80..c6eed91e0 100644 --- a/packages/subgraph/src/registries/daoRegistry.ts +++ b/packages/subgraph/src/registries/daoRegistry.ts @@ -1,19 +1,21 @@ import {DAORegistered} from '../../generated/DAORegistry/DAORegistry'; -import {DaoTemplateV1_0_0, DaoTemplateV1_3_0} from '../../generated/templates'; import {Dao} from '../../generated/schema'; +import {DaoTemplateV1_0_0, DaoTemplateV1_3_0} from '../../generated/templates'; +import {generateDaoEntityId} from '@aragon/osx-commons-subgraph'; import {dataSource} from '@graphprotocol/graph-ts'; // blocklists of addresses for which we don't index the subdomain. // Put the reason next to the address as a comment const subdomain_blocklist_mainnet = [ - '0x16070493aa513f91fc8957f14b7b7c6c0c41fbac' // domain squatting lido.dao.eth + '0x16070493aa513f91fc8957f14b7b7c6c0c41fbac', // domain squatting lido.dao.eth ]; export function handleDAORegistered(event: DAORegistered): void { - let id = event.params.dao.toHexString(); // use dao address as id, because it should not repeat - let entity = new Dao(id); + let daoAddress = event.params.dao; // use dao address as id, because it should not repeat + let daoEntityId = generateDaoEntityId(daoAddress); + let entity = new Dao(daoEntityId); - if (!isInSubdomainBlocklist(id)) { + if (!isInSubdomainBlocklist(daoEntityId)) { entity.subdomain = event.params.subdomain; } @@ -21,8 +23,8 @@ export function handleDAORegistered(event: DAORegistered): void { entity.createdAt = event.block.timestamp; // subscribe to templates - DaoTemplateV1_0_0.create(event.params.dao); - DaoTemplateV1_3_0.create(event.params.dao); + DaoTemplateV1_0_0.create(daoAddress); + DaoTemplateV1_3_0.create(daoAddress); entity.save(); } diff --git a/packages/subgraph/src/registries/pluginRepoRegistry.ts b/packages/subgraph/src/registries/pluginRepoRegistry.ts index 0c2e21be8..1c9bdb73b 100644 --- a/packages/subgraph/src/registries/pluginRepoRegistry.ts +++ b/packages/subgraph/src/registries/pluginRepoRegistry.ts @@ -1,15 +1,17 @@ import {PluginRepoRegistered} from '../../generated/PluginRepoRegistry/PluginRepoRegistry'; -import {PluginRepoTemplate} from '../../generated/templates'; import {PluginRepo} from '../../generated/schema'; +import {PluginRepoTemplate} from '../../generated/templates'; +import {generatePluginRepoEntityId} from '@aragon/osx-commons-subgraph'; export function handlePluginRepoRegistered(event: PluginRepoRegistered): void { - let id = event.params.pluginRepo.toHexString(); - let entity = new PluginRepo(id); + let pluginRepoAddress = event.params.pluginRepo; + let pluginRepoEntityId = generatePluginRepoEntityId(pluginRepoAddress); + let entity = new PluginRepo(pluginRepoEntityId); entity.subdomain = event.params.subdomain; // subscribe to templates - PluginRepoTemplate.create(event.params.pluginRepo); + PluginRepoTemplate.create(pluginRepoAddress); entity.save(); } diff --git a/packages/subgraph/src/utils/bytes.ts b/packages/subgraph/src/utils/bytes.ts index 57734c0ae..5e4a7484b 100644 --- a/packages/subgraph/src/utils/bytes.ts +++ b/packages/subgraph/src/utils/bytes.ts @@ -1,4 +1,4 @@ -import {BigInt} from '@graphprotocol/graph-ts'; +import {BigInt, Bytes} from '@graphprotocol/graph-ts'; export function bigIntToBytes32(input: BigInt): string { const hexString = input @@ -7,3 +7,7 @@ export function bigIntToBytes32(input: BigInt): string { .padStart(64, '0'); // pad left with '0' until reaching target length of 32 bytes return `0x${hexString}`; // add 0x to the start } + +export function getMethodSignature(data: Bytes): string { + return data.toHexString().slice(0, 10); +} diff --git a/packages/subgraph/src/utils/constants.ts b/packages/subgraph/src/utils/constants.ts index 2b0ded6d9..c12ba6ac7 100644 --- a/packages/subgraph/src/utils/constants.ts +++ b/packages/subgraph/src/utils/constants.ts @@ -23,10 +23,10 @@ export const VOTING_MODE_INDEXES = new Map() .set('EarlyExecution', '1') .set('VoteReplacement', '2'); -export const TOKEN_VOTING_INTERFACE = '0x50eb001e'; -export const ADDRESSLIST_VOTING_INTERFACE = '0x5f21eb8b'; -export const ADMIN_INTERFACE = '0xa5793356'; -export const MULTISIG_INTERFACE = '0x8f852786'; -export const WRAPPED_ERC20_INTERFACE = '0x0f13099a'; +export const TOKEN_VOTING_INTERFACE_ID = '0x50eb001e'; +export const ADDRESSLIST_VOTING_INTERFACE_ID = '0x5f21eb8b'; +export const ADMIN_INTERFACE_ID = '0xa5793356'; +export const MULTISIG_INTERFACE_ID = '0x8f852786'; +export const GOVERNANCE_WRAPPED_ERC20_INTERFACE_ID = '0x0f13099a'; export const RATIO_BASE = '1000000'; // 10**6 diff --git a/packages/subgraph/src/utils/ids.ts b/packages/subgraph/src/utils/ids.ts new file mode 100644 index 000000000..55ee89beb --- /dev/null +++ b/packages/subgraph/src/utils/ids.ts @@ -0,0 +1,57 @@ +import { + generateEntityIdFromAddress, + generateEntityIdFromBytes, +} from '@aragon/osx-commons-subgraph'; +import {Address, BigInt, Bytes} from '@graphprotocol/graph-ts'; + +export function generateTokenEntityId(tokenAddress: Address): string { + return generateEntityIdFromAddress(tokenAddress); +} + +export function generateERC1155TransferEntityId( + txHash: Bytes, + logIndex: BigInt, + actionIndex: number, + batchIndex: number +): string { + return [ + generateEntityIdFromBytes(txHash), + logIndex.toString(), + actionIndex.toString(), + batchIndex.toString(), + ].join('_'); +} + +export function generateVoterEntityId( + memberEntityId: string, + proposalId: string +): string { + return [memberEntityId, proposalId].join('_'); +} + +export function generateMemberEntityId( + pluginAddress: Address, + memberAddress: Address +): string { + return [ + generateEntityIdFromAddress(pluginAddress), + generateEntityIdFromAddress(memberAddress), + ].join('_'); +} + +export function generateVoteEntityId( + memberAddress: Address, + proposalId: string +): string { + return [generateEntityIdFromAddress(memberAddress), proposalId].join('_'); +} + +export function generateAdministratorAdminPluginEntityId( + pluginAddress: Address, + administratorAddress: Address +): string { + return [ + generateEntityIdFromAddress(pluginAddress), + generateEntityIdFromAddress(administratorAddress), + ].join('_'); +} diff --git a/packages/subgraph/src/utils/proposals.ts b/packages/subgraph/src/utils/proposals.ts index 9565c3efc..fbc8fe439 100644 --- a/packages/subgraph/src/utils/proposals.ts +++ b/packages/subgraph/src/utils/proposals.ts @@ -1,5 +1,5 @@ -import {Address, BigInt} from '@graphprotocol/graph-ts'; import {bigIntToBytes32} from './bytes'; +import {Address, BigInt} from '@graphprotocol/graph-ts'; export function getProposalId( plugin: Address, diff --git a/packages/subgraph/src/utils/tokens/common.ts b/packages/subgraph/src/utils/tokens/common.ts index 3e89dd0cc..4b61009aa 100644 --- a/packages/subgraph/src/utils/tokens/common.ts +++ b/packages/subgraph/src/utils/tokens/common.ts @@ -15,7 +15,7 @@ export const ERC1155_safeBatchTransferFrom = '0x2eb2c2d6'; // `bytes4(keccak256( export enum TransferType { Withdraw, - Deposit + Deposit, } export const DECODE_OFFSET = @@ -49,11 +49,7 @@ export function getTokenIdBalanceId( token: string, tokenId: BigInt ): string { - return daoId - .concat('_') - .concat(token) - .concat('_') - .concat(tokenId.toString()); + return daoId.concat('_').concat(token).concat('_').concat(tokenId.toString()); } // Unique ID generation for ERC1155 transfers diff --git a/packages/subgraph/src/utils/tokens/erc1155.ts b/packages/subgraph/src/utils/tokens/erc1155.ts index da8fe2a32..0df31fd8c 100644 --- a/packages/subgraph/src/utils/tokens/erc1155.ts +++ b/packages/subgraph/src/utils/tokens/erc1155.ts @@ -1,12 +1,13 @@ -import {Address, BigInt, Bytes, ethereum, log} from '@graphprotocol/graph-ts'; import { ERC1155Balance, ERC1155Contract, ERC1155TokenIdBalance, - ERC1155Transfer + ERC1155Transfer, } from '../../../generated/schema'; import {ERC1155} from '../../../generated/templates/DaoTemplateV1_0_0/ERC1155'; +import {getMethodSignature} from '../bytes'; import {supportsInterface} from '../erc165'; +import {generateTokenEntityId} from '../ids'; import { DECODE_OFFSET, ERC1155_INTERFACE_ID, @@ -16,8 +17,10 @@ import { TransferType, getBalanceId, getERC1155TransferId, - getTokenIdBalanceId + getTokenIdBalanceId, } from './common'; +import {generateDaoEntityId} from '@aragon/osx-commons-subgraph'; +import {Address, BigInt, Bytes, ethereum} from '@graphprotocol/graph-ts'; export function supportsERC1155(token: Address): bool { // Double check that it's ERC1155 by calling supportsInterface checks. @@ -198,88 +201,132 @@ export function handleERC1155Action( proposalId: string, actionIndex: number, event: ethereum.Event -): void { +): bool { let contract = fetchERC1155(token); if (!contract) { - return; + return false; } - let functionSelector = data.toHexString().substring(0, 10); + let functionSelector = getMethodSignature(data); + let decodeABI = determineERC1155DecodeABI(functionSelector); + + // If decodeABI is not determined, return false + if (!decodeABI) return false; + let calldata = DECODE_OFFSET + data.toHexString().slice(10); + let bytes = Bytes.fromHexString(calldata); + let decoded = ethereum.decode(decodeABI as string, bytes); - let decodeABI = ''; + if (!decoded) { + return false; + } + + let tuple = decoded.toTuple(); if (functionSelector == ERC1155_safeTransferFrom) { - decodeABI = '(address,address,uint256,uint256,bytes)'; + handleERC1155SingleTransfer( + tuple, + dao, + token, + proposalId, + event, + actionIndex + ); + } else if (functionSelector == ERC1155_safeBatchTransferFrom) { + handleERC1155BatchTransfer( + tuple, + dao, + token, + proposalId, + event, + actionIndex + ); } - if (functionSelector == ERC1155_safeBatchTransferFrom) { - decodeABI = '(address,address,uint256[],uint256[],bytes)'; + return true; +} + +function determineERC1155DecodeABI(functionSelector: string): string | null { + if (functionSelector == ERC1155_safeTransferFrom) { + return '(address,address,uint256,uint256,bytes)'; } - let bytes = Bytes.fromHexString(calldata); - let decoded = ethereum.decode(decodeABI, bytes); - if (!decoded) { - return; + + if (functionSelector == ERC1155_safeBatchTransferFrom) { + return '(address,address,uint256[],uint256[],bytes)'; } - let tuple = decoded.toTuple(); + return null; +} - let from = tuple[0].toAddress(); - let to = tuple[1].toAddress(); - if (functionSelector == ERC1155_safeTransferFrom) { - // in single transfer create a single transfer - let tokenId = tuple[2].toBigInt(); - let amount = tuple[3].toBigInt(); - // generate unique transfer id +function handleERC1155SingleTransfer( + tuple: ethereum.Tuple, + dao: Address, + token: Address, + proposalId: string, + event: ethereum.Event, + actionIndex: number +): void { + let tokenId = tuple[2].toBigInt(); + let amount = tuple[3].toBigInt(); + + // generate unique transfer id + let transferId = getERC1155TransferId( + event.transaction.hash, + event.transactionLogIndex, + actionIndex, + 0 + ); + + createErc1155Transfer( + transferId, + dao, // operator field, the operator is going to be the dao since is the one executing the action + tuple[0].toAddress(), + tuple[1].toAddress(), + dao, + token, + tokenId, + amount, + proposalId, + event.transaction.hash, + event.block.timestamp + ); +} + +function handleERC1155BatchTransfer( + tuple: ethereum.Tuple, + dao: Address, + token: Address, + proposalId: string, + event: ethereum.Event, + actionIndex: number +): void { + let tokenIds = tuple[2].toBigIntArray(); + let amounts = tuple[3].toBigIntArray(); + + // in batch transfer iterate over the tokenIds and create a transfer for each + for (let i = 0; i < tokenIds.length; i++) { + // generate unique transfer id by adding the index of the tokenIds array let transferId = getERC1155TransferId( event.transaction.hash, event.transactionLogIndex, actionIndex, - 0 + i ); - // create transfer + createErc1155Transfer( transferId, dao, // operator field, the operator is going to be the dao since is the one executing the action - from, - to, + tuple[0].toAddress(), + tuple[1].toAddress(), dao, token, - tokenId, - amount, + tokenIds[i], + amounts[i], proposalId, event.transaction.hash, event.block.timestamp ); } - if (functionSelector == ERC1155_safeBatchTransferFrom) { - let tokenIds = tuple[2].toBigIntArray(); - let amounts = tuple[3].toBigIntArray(); - // in batch transfer iterate over the tokenIds and create a transfer for each - for (let i = 0; i < tokenIds.length; i++) { - // generate unique transfer id by adding the index of the tokenIds array - let transferId = getERC1155TransferId( - event.transaction.hash, - event.transactionLogIndex, - actionIndex, - i - ); - // create transfer - createErc1155Transfer( - transferId, - dao, // operator field, the operator is going to be the dao since is the one executing the action - from, - to, - dao, - token, - tokenIds[i], - amounts[i], - proposalId, - event.transaction.hash, - event.block.timestamp - ); - } - } } function createErc1155Transfer( @@ -295,13 +342,15 @@ function createErc1155Transfer( txHash: Bytes, timestamp: BigInt ): void { + let daoEntityId = generateDaoEntityId(dao); + let tokenEntityId = generateTokenEntityId(token); // create transfer let transfer = new ERC1155Transfer(transferId); transfer.from = from; transfer.to = to; transfer.operator = operator; - transfer.dao = dao.toHexString(); - transfer.token = token.toHexString(); + transfer.dao = daoEntityId; + transfer.token = tokenEntityId; transfer.amount = amount; transfer.tokenId = tokenId; transfer.proposal = proposalId; @@ -326,8 +375,8 @@ function createErc1155Transfer( // 2. dao calls transferFrom as an action to transfer it from `y` to itself. transfer.type = 'Deposit'; updateERC1155Balance( - dao.toHexString(), - token.toHexString(), + daoEntityId, + tokenEntityId, tokenId, amount, timestamp, @@ -337,8 +386,8 @@ function createErc1155Transfer( transfer.type = 'Withdraw'; updateERC1155Balance( - dao.toHexString(), - token.toHexString(), + daoEntityId, + tokenEntityId, tokenId, amount, timestamp, diff --git a/packages/subgraph/src/utils/tokens/erc20.ts b/packages/subgraph/src/utils/tokens/erc20.ts index 5858a5f2f..09d6d0df0 100644 --- a/packages/subgraph/src/utils/tokens/erc20.ts +++ b/packages/subgraph/src/utils/tokens/erc20.ts @@ -1,22 +1,28 @@ -import {Address, BigInt, Bytes, Value, ethereum} from '@graphprotocol/graph-ts'; import { ERC20Balance, ERC20Contract, ERC20Transfer, - ERC20WrapperContract + ERC20WrapperContract, } from '../../../generated/schema'; import {ERC20} from '../../../generated/templates/DaoTemplateV1_0_0/ERC20'; import {GovernanceWrappedERC20} from '../../../generated/templates/TokenVoting/GovernanceWrappedERC20'; -import {ERC20_transfer, ERC20_transferFrom, getTransferId} from './common'; +import {GOVERNANCE_WRAPPED_ERC20_INTERFACE_ID} from '../../utils/constants'; import {supportsInterface} from '../erc165'; -import {WRAPPED_ERC20_INTERFACE} from '../constants'; +import {generateTokenEntityId} from '../ids'; +import {ERC20_transfer, ERC20_transferFrom} from './common'; +import { + generateBalanceEntityId, + generateDaoEntityId, + generateTransferEntityId, +} from '@aragon/osx-commons-subgraph'; +import {Address, BigInt, Bytes, ethereum} from '@graphprotocol/graph-ts'; export function supportsERC20Wrapped(token: Address): bool { // Double check that it's ERC20Wrapped by calling supportsInterface checks. let erc20Wrapped = GovernanceWrappedERC20.bind(token); let introspection_wrapped_erc20 = supportsInterface( erc20Wrapped, - WRAPPED_ERC20_INTERFACE + GOVERNANCE_WRAPPED_ERC20_INTERFACE_ID ); // GovernanceWrappedERC20 if (!introspection_wrapped_erc20) { return false; @@ -29,17 +35,18 @@ export function supportsERC20Wrapped(token: Address): bool { return introspection_ffffffff; } -export function fetchWrappedERC20( +export function fetchOrCreateWrappedERC20Entity( address: Address ): ERC20WrapperContract | null { + const tokenEntityId = generateTokenEntityId(address); let wrappedErc20 = GovernanceWrappedERC20.bind(address); // try load entry - let contract = ERC20WrapperContract.load(address.toHexString()); + let contract = ERC20WrapperContract.load(tokenEntityId); if (contract != null) { return contract; } - contract = new ERC20WrapperContract(address.toHexString()); + contract = new ERC20WrapperContract(tokenEntityId); let try_name = wrappedErc20.try_name(); let try_symbol = wrappedErc20.try_symbol(); @@ -52,7 +59,7 @@ export function fetchWrappedERC20( return null; } // get and save the underliying contract - let underlyingContract = fetchERC20(underlying.value); + let underlyingContract = fetchOrCreateERC20Entity(underlying.value); if (!underlyingContract) { return null; } @@ -65,16 +72,19 @@ export function fetchWrappedERC20( return contract; } -export function fetchERC20(address: Address): ERC20Contract | null { +export function fetchOrCreateERC20Entity( + address: Address +): ERC20Contract | null { + const tokenEntityId = generateTokenEntityId(address); let erc20 = ERC20.bind(address); // Try load entry - let contract = ERC20Contract.load(address.toHexString()); + let contract = ERC20Contract.load(tokenEntityId); if (contract != null) { return contract; } - contract = new ERC20Contract(address.toHexString()); + contract = new ERC20Contract(tokenEntityId); let try_name = erc20.try_name(); let try_symbol = erc20.try_symbol(); @@ -95,6 +105,37 @@ export function fetchERC20(address: Address): ERC20Contract | null { return contract; } +/** + * @dev Identifies the type of ERC20 token (wrapped or regular), fetches or creates the corresponding entity, and returns its entity ID. + * + * 1. Checks whether the token supports wrapped ERC20. + * 2. Fetches the existing entity if it exists. + * 3. Creates a new entity if it doesn't exist. + * + * @param token The address of the token to be identified. + * @return entityId The entity ID of the ERC20 token if it's either wrapped or regular, null otherwise. + */ +export function identifyAndFetchOrCreateERC20TokenEntity( + token: Address +): string | null { + let tokenAddress: string; + if (supportsERC20Wrapped(token)) { + let contract = fetchOrCreateWrappedERC20Entity(token); + if (!contract) { + return null; + } + tokenAddress = contract.id; + } else { + let contract = fetchOrCreateERC20Entity(token); + if (!contract) { + return null; + } + tokenAddress = contract.id; + } + + return tokenAddress; +} + export function updateERC20Balance( token: Address, dao: Address, @@ -108,15 +149,16 @@ export function updateERC20Balance( return; } - let daoId = dao.toHexString(); - let balanceId = daoId.concat('_').concat(token.toHexString()); + let daoEntityId = generateDaoEntityId(dao); + let balanceEntityId = generateBalanceEntityId(dao, token); + let tokenEntityId = generateTokenEntityId(token); - let erc20Balance = ERC20Balance.load(balanceId); + let erc20Balance = ERC20Balance.load(balanceEntityId); if (!erc20Balance) { - erc20Balance = new ERC20Balance(balanceId); - erc20Balance.dao = daoId; - erc20Balance.token = token.toHexString(); + erc20Balance = new ERC20Balance(balanceEntityId); + erc20Balance.dao = daoEntityId; + erc20Balance.token = tokenEntityId; erc20Balance.balance = BigInt.zero(); } @@ -133,8 +175,8 @@ export function handleERC20Action( actionIndex: number, event: ethereum.Event ): void { - let contract = fetchERC20(token); - if (!contract) { + let tokenAddress = identifyAndFetchOrCreateERC20TokenEntity(token); + if (!tokenAddress) { return; } @@ -175,23 +217,23 @@ export function handleERC20Action( amount = tuple[2].toBigInt(); } - let daoId = dao.toHexString(); + let daoEntityId = generateDaoEntityId(dao); - let id = getTransferId( + let transferEntityId = generateTransferEntityId( event.transaction.hash, event.transactionLogIndex, - actionIndex + actionIndex as i32 ); - let transfer = new ERC20Transfer(id); + let transfer = new ERC20Transfer(transferEntityId); transfer.from = from; transfer.to = to; - transfer.dao = daoId; + transfer.dao = daoEntityId; transfer.amount = amount; transfer.txHash = event.transaction.hash; transfer.createdAt = event.block.timestamp; - transfer.token = contract.id; + transfer.token = tokenAddress as string; transfer.proposal = proposalId; // If from/to both aren't equal to dao, it means @@ -226,24 +268,28 @@ export function handleERC20Deposit( amount: BigInt, event: ethereum.Event ): void { - let contract = fetchERC20(token); - if (!contract) { + let tokenAddress = identifyAndFetchOrCreateERC20TokenEntity(token); + if (!tokenAddress) { return; } - let daoId = dao.toHexString(); + let daoEntityId = generateDaoEntityId(dao); - let id = getTransferId(event.transaction.hash, event.transactionLogIndex, 0); + let transferEntityId = generateTransferEntityId( + event.transaction.hash, + event.transactionLogIndex, + 0 + ); - let erc20Transfer = new ERC20Transfer(id); + let erc20Transfer = new ERC20Transfer(transferEntityId); erc20Transfer.from = from; erc20Transfer.to = dao; - erc20Transfer.dao = daoId; + erc20Transfer.dao = daoEntityId; erc20Transfer.amount = amount; erc20Transfer.txHash = event.transaction.hash; erc20Transfer.createdAt = event.block.timestamp; - erc20Transfer.token = contract.id; + erc20Transfer.token = tokenAddress as string; erc20Transfer.type = 'Deposit'; erc20Transfer.save(); diff --git a/packages/subgraph/src/utils/tokens/erc721.ts b/packages/subgraph/src/utils/tokens/erc721.ts index f6ac705ed..0ce05af8c 100644 --- a/packages/subgraph/src/utils/tokens/erc721.ts +++ b/packages/subgraph/src/utils/tokens/erc721.ts @@ -1,17 +1,23 @@ -import {Address, BigInt, Bytes, ethereum} from '@graphprotocol/graph-ts'; import { ERC721Balance, ERC721Contract, - ERC721Transfer + ERC721Transfer, } from '../../../generated/schema'; import {ERC721} from '../../../generated/templates/DaoTemplateV1_0_0/ERC721'; +import {getMethodSignature} from '../bytes'; import {supportsInterface} from '../erc165'; -import {DECODE_OFFSET, getTransferId, TransferType} from './common'; +import {generateTokenEntityId} from '../ids'; +import {DECODE_OFFSET, TransferType} from './common'; import { ERC721_safeTransferFromNoData, ERC721_safeTransferFromWithData, - ERC721_transferFrom + ERC721_transferFrom, } from './common'; +import { + generateDaoEntityId, + generateTransferEntityId, +} from '@aragon/osx-commons-subgraph'; +import {Address, BigInt, Bytes, ethereum} from '@graphprotocol/graph-ts'; function supportsERC721(token: Address): bool { // Double check that it's ERC721 by calling supportsInterface checks. @@ -103,26 +109,26 @@ export function handleERC721Received( let from = tuple[1].toAddress(); let tokenId = tuple[2].toBigInt(); - let daoId = dao.toHexString(); + let daoEntityId = generateDaoEntityId(dao); updateERC721Balance( - daoId, - token.toHexString(), + daoEntityId, + generateTokenEntityId(token), tokenId, event.block.timestamp, TransferType.Deposit ); - let transferId = getTransferId( + let transferEntityId = generateTransferEntityId( event.transaction.hash, event.transactionLogIndex, 0 ); - let transfer = new ERC721Transfer(transferId); + let transfer = new ERC721Transfer(transferEntityId); transfer.from = from; transfer.to = dao; - transfer.dao = daoId; + transfer.dao = daoEntityId; transfer.token = contract.id; transfer.tokenId = tokenId; transfer.txHash = event.transaction.hash; @@ -138,97 +144,106 @@ export function handleERC721Action( proposalId: string, actionIndex: number, event: ethereum.Event -): void { +): bool { let contract = fetchERC721(token); - if (!contract) { - return; - } - - let functionSelector = data.toHexString().substring(0, 10); - let calldata = data.toHexString().slice(10); - - let decodeABI = ''; - - if ( - functionSelector == ERC721_transferFrom || - functionSelector == ERC721_safeTransferFromNoData - ) { - decodeABI = '(address,address,uint256)'; - } - - if (functionSelector == ERC721_safeTransferFromWithData) { - decodeABI = '(address,address,uint256,bytes)'; - calldata = DECODE_OFFSET + calldata; - } + if (!contract) return false; - let decoded = ethereum.decode(decodeABI, Bytes.fromHexString(calldata)); + let functionSelector = getMethodSignature(data); + let decodeABI = determineERC721DecodeABI(functionSelector); - if (!decoded) { - return; - } - - let tuple = decoded.toTuple(); - - let from = tuple[0].toAddress(); - let to = tuple[1].toAddress(); - let tokenId = tuple[2].toBigInt(); + if (!decodeABI) return false; - let daoId = dao.toHexString(); - - let transferId = getTransferId( - event.transaction.hash, - event.transactionLogIndex, + let calldata = getCalldata(functionSelector, data); + let decoded = ethereum.decode( + decodeABI as string, + Bytes.fromHexString(calldata) + ); + if (!decoded) return false; + + let transfer = createERC721Transfer( + decoded.toTuple(), + contract, + dao, + event, + proposalId, actionIndex ); - let transfer = new ERC721Transfer(transferId); - transfer.from = from; - transfer.to = to; - transfer.dao = daoId; - transfer.token = contract.id; - transfer.tokenId = tokenId; - transfer.proposal = proposalId; - transfer.txHash = event.transaction.hash; - transfer.createdAt = event.block.timestamp; - - if (from == dao && to == dao) { + if (transfer.from == dao && transfer.to == dao) { transfer.type = 'Withdraw'; - transfer.save(); - return; - } - - // If from/to both aren't equal to dao, it means - // dao must have been approved for the `tokenId` - // and played the role of transfering between 2 parties. - if (from != dao && to != dao) { + } else if (transfer.from != dao && transfer.to != dao) { + // If from/to both aren't equal to dao, it means + // dao must have been approved for the `tokenId` + // and played the role of transfering between 2 parties. transfer.type = 'ExternalTransfer'; - transfer.save(); - return; - } - - if (from != dao && to == dao) { + } else if (transfer.from != dao && transfer.to == dao) { // 1. some party `y` approved `x` tokenId to the dao. // 2. dao calls transferFrom as an action to transfer it from `y` to itself. transfer.type = 'Deposit'; - updateERC721Balance( - daoId, - token.toHexString(), - tokenId, + transfer.dao, + transfer.token, + transfer.tokenId, event.block.timestamp, TransferType.Deposit ); } else { transfer.type = 'Withdraw'; - updateERC721Balance( - daoId, - token.toHexString(), - tokenId, + transfer.dao, + transfer.token, + transfer.tokenId, event.block.timestamp, TransferType.Withdraw ); } transfer.save(); + return true; +} + +function determineERC721DecodeABI(functionSelector: string): string | null { + if ( + functionSelector == ERC721_transferFrom || + functionSelector == ERC721_safeTransferFromNoData + ) { + return '(address,address,uint256)'; + } else if (functionSelector == ERC721_safeTransferFromWithData) { + return '(address,address,uint256,bytes)'; + } + return null; +} + +function getCalldata(functionSelector: string, data: Bytes): string { + if (functionSelector == ERC721_safeTransferFromWithData) { + return DECODE_OFFSET + data.toHexString().slice(10); + } + return data.toHexString().slice(10); +} + +function createERC721Transfer( + tuple: ethereum.Tuple, + contract: ERC721Contract, + dao: Address, + event: ethereum.Event, + proposalId: string, + actionIndex: number +): ERC721Transfer { + let transferEntityId = generateTransferEntityId( + event.transaction.hash, + event.transactionLogIndex, + actionIndex as i32 + ); + let daoEntityId = generateDaoEntityId(dao); + let transfer = new ERC721Transfer(transferEntityId); + transfer.from = tuple[0].toAddress(); + transfer.to = tuple[1].toAddress(); + transfer.dao = daoEntityId; + transfer.token = contract.id; + transfer.tokenId = tuple[2].toBigInt(); + transfer.proposal = proposalId; + transfer.txHash = event.transaction.hash; + transfer.createdAt = event.block.timestamp; + + return transfer; } diff --git a/packages/subgraph/src/utils/tokens/eth.ts b/packages/subgraph/src/utils/tokens/eth.ts index 792c477ff..415203a41 100644 --- a/packages/subgraph/src/utils/tokens/eth.ts +++ b/packages/subgraph/src/utils/tokens/eth.ts @@ -1,20 +1,29 @@ -import {Address, BigInt, ethereum} from '@graphprotocol/graph-ts'; import {NativeBalance, NativeTransfer} from '../../../generated/schema'; import {ADDRESS_ZERO} from '../constants'; -import {getTransferId, TransferType} from './common'; +import {TransferType} from './common'; +import { + generateBalanceEntityId, + generateDaoEntityId, + generateTransferEntityId, +} from '@aragon/osx-commons-subgraph'; +import {Address, BigInt, ethereum} from '@graphprotocol/graph-ts'; export function updateNativeBalance( - daoId: string, + dao: Address, amount: BigInt, timestamp: BigInt, type: TransferType ): void { - let balanceId = daoId.concat('_').concat(ADDRESS_ZERO); - let nativeBalance = NativeBalance.load(balanceId); + let balanceEntityId = generateBalanceEntityId( + dao, + Address.fromString(ADDRESS_ZERO) + ); + let daoEntityId = generateDaoEntityId(dao); + let nativeBalance = NativeBalance.load(balanceEntityId); if (!nativeBalance) { - nativeBalance = new NativeBalance(balanceId); - nativeBalance.dao = daoId; + nativeBalance = new NativeBalance(balanceEntityId); + nativeBalance.dao = daoEntityId; nativeBalance.balance = BigInt.zero(); } @@ -33,14 +42,18 @@ export function handleNativeDeposit( reference: string, event: ethereum.Event ): void { - let daoId = dao.toHexString(); + let daoEntityId = generateDaoEntityId(dao); - let id = getTransferId(event.transaction.hash, event.transactionLogIndex, 0); + let transferEntityId = generateTransferEntityId( + event.transaction.hash, + event.transactionLogIndex, + 0 + ); - let transfer = new NativeTransfer(id); + let transfer = new NativeTransfer(transferEntityId); transfer.from = from; transfer.to = dao; - transfer.dao = daoId; + transfer.dao = daoEntityId; transfer.amount = amount; transfer.reference = reference; transfer.txHash = event.transaction.hash; @@ -52,12 +65,7 @@ export function handleNativeDeposit( return; } - updateNativeBalance( - daoId, - amount, - event.block.timestamp, - TransferType.Deposit - ); + updateNativeBalance(dao, amount, event.block.timestamp, TransferType.Deposit); } export function handleNativeAction( @@ -69,18 +77,18 @@ export function handleNativeAction( actionIndex: number, event: ethereum.Event ): void { - let daoId = dao.toHexString(); + let daoEntityId = generateDaoEntityId(dao); - let id = getTransferId( + let transferEntityId = generateTransferEntityId( event.transaction.hash, event.transactionLogIndex, - actionIndex + actionIndex as i32 ); - let transfer = new NativeTransfer(id); + let transfer = new NativeTransfer(transferEntityId); transfer.from = dao; transfer.to = to; - transfer.dao = daoId; + transfer.dao = daoEntityId; transfer.amount = amount; transfer.reference = reference; transfer.txHash = event.transaction.hash; @@ -94,7 +102,7 @@ export function handleNativeAction( } updateNativeBalance( - daoId, + dao, amount, event.block.timestamp, TransferType.Withdraw diff --git a/packages/subgraph/tests/addresslist-voting/addresslist-voting.test.ts b/packages/subgraph/tests/addresslist-voting/addresslist-voting.test.ts index 9303339ca..3f54a88c2 100644 --- a/packages/subgraph/tests/addresslist-voting/addresslist-voting.test.ts +++ b/packages/subgraph/tests/addresslist-voting/addresslist-voting.test.ts @@ -1,19 +1,20 @@ -import {assert, clearStore, test} from 'matchstick-as/assembly/index'; -import {Address, BigInt, Bytes} from '@graphprotocol/graph-ts'; - +import { + AddresslistVotingPlugin, + AddresslistVotingVoter, +} from '../../generated/schema'; import { handleMembersAdded, handleVoteCast, handleProposalExecuted, handleMembersRemoved, handleVotingSettingsUpdated, - _handleProposalCreated + _handleProposalCreated, } from '../../src/packages/addresslist/addresslist-voting'; -import { - AddresslistVotingPlugin, - AddresslistVotingVoter -} from '../../generated/schema'; import {VOTING_MODES} from '../../src/utils/constants'; +import { + generateMemberEntityId, + generateVoteEntityId, +} from '../../src/utils/ids'; import { ADDRESS_ONE, ADDRESS_TWO, @@ -33,13 +34,9 @@ import { SNAPSHOT_BLOCK, TOTAL_VOTING_POWER, ALLOW_FAILURE_MAP, - PROPOSAL_ENTITY_ID + PROPOSAL_ENTITY_ID, } from '../constants'; -import { - createDummyActions, - createGetProposalCall, - createTotalVotingPowerCall -} from '../utils'; +import {createGetProposalCall, createTotalVotingPowerCall} from '../utils'; import { createNewMembersAddedEvent, createNewVoteCastEvent, @@ -48,24 +45,36 @@ import { createNewProposalCreatedEvent, createNewVotingSettingsUpdatedEvent, getProposalCountCall, - createAddresslistVotingProposalEntityState + createAddresslistVotingProposalEntityState, } from './utils'; +import { + generatePluginEntityId, + createDummyAction, +} from '@aragon/osx-commons-subgraph'; +import {Address, BigInt} from '@graphprotocol/graph-ts'; +import {assert, clearStore, test} from 'matchstick-as/assembly/index'; -let actions = createDummyActions(DAO_TOKEN_ADDRESS, '0', '0x00000000'); +let actions = [createDummyAction(DAO_TOKEN_ADDRESS, '0', '0x00000000')]; + +const daoAddress = Address.fromString(DAO_ADDRESS); +const daoEntityId = generatePluginEntityId(daoAddress); +const pluginAddress = Address.fromString(CONTRACT_ADDRESS); +const pluginEntityId = generatePluginEntityId(pluginAddress); +const memberOneAddress = Address.fromString(ADDRESS_ONE); +const memberTwoAddress = Address.fromString(ADDRESS_TWO); +const memberOneHexString = memberOneAddress.toHexString(); test('Run AddresslistVoting (handleProposalCreated) mappings with mock event', () => { // create state - let addresslistVotingPlugin = new AddresslistVotingPlugin( - Address.fromString(CONTRACT_ADDRESS).toHexString() - ); - addresslistVotingPlugin.dao = DAO_ADDRESS; - addresslistVotingPlugin.pluginAddress = Bytes.fromHexString(CONTRACT_ADDRESS); + let addresslistVotingPlugin = new AddresslistVotingPlugin(pluginEntityId); + addresslistVotingPlugin.dao = daoEntityId; + addresslistVotingPlugin.pluginAddress = pluginAddress; addresslistVotingPlugin.save(); // create calls - getProposalCountCall(CONTRACT_ADDRESS, '1'); + getProposalCountCall(pluginEntityId, '1'); createGetProposalCall( - CONTRACT_ADDRESS, + pluginEntityId, PLUGIN_PROPOSAL_ID, true, false, @@ -88,7 +97,7 @@ test('Run AddresslistVoting (handleProposalCreated) mappings with mock event', ( ); createTotalVotingPowerCall( - CONTRACT_ADDRESS, + pluginEntityId, SNAPSHOT_BLOCK, TOTAL_VOTING_POWER ); @@ -106,9 +115,7 @@ test('Run AddresslistVoting (handleProposalCreated) mappings with mock event', ( ); // handle event - _handleProposalCreated(event, DAO_ADDRESS, STRING_DATA); - - let packageId = Address.fromString(CONTRACT_ADDRESS).toHexString(); + _handleProposalCreated(event, daoEntityId, STRING_DATA); // checks assert.fieldEquals( @@ -121,13 +128,13 @@ test('Run AddresslistVoting (handleProposalCreated) mappings with mock event', ( 'AddresslistVotingProposal', PROPOSAL_ENTITY_ID, 'dao', - DAO_ADDRESS + daoEntityId ); assert.fieldEquals( 'AddresslistVotingProposal', PROPOSAL_ENTITY_ID, 'plugin', - packageId + pluginEntityId ); assert.fieldEquals( 'AddresslistVotingProposal', @@ -233,7 +240,7 @@ test('Run AddresslistVoting (handleVoteCast) mappings with mock event', () => { // create calls createGetProposalCall( - CONTRACT_ADDRESS, + pluginEntityId, PLUGIN_PROPOSAL_ID, true, false, @@ -256,7 +263,7 @@ test('Run AddresslistVoting (handleVoteCast) mappings with mock event', () => { ); createTotalVotingPowerCall( - CONTRACT_ADDRESS, + pluginEntityId, SNAPSHOT_BLOCK, TOTAL_VOTING_POWER ); @@ -264,33 +271,33 @@ test('Run AddresslistVoting (handleVoteCast) mappings with mock event', () => { // create event let event = createNewVoteCastEvent( PLUGIN_PROPOSAL_ID, - ADDRESS_ONE, + memberOneHexString, '2', // yes '1', // votingPower - CONTRACT_ADDRESS + pluginEntityId ); handleVoteCast(event); // checks - let voteEntityID = ADDRESS_ONE + '_' + proposal.id; - assert.fieldEquals('AddresslistVotingVote', voteEntityID, 'id', voteEntityID); + const voteEntityId = generateVoteEntityId(memberOneAddress, proposal.id); + assert.fieldEquals('AddresslistVotingVote', voteEntityId, 'id', voteEntityId); assert.fieldEquals( 'AddresslistVotingVote', - voteEntityID, + voteEntityId, 'voteReplaced', 'false' ); assert.fieldEquals( 'AddresslistVotingVote', - voteEntityID, + voteEntityId, 'updatedAt', BigInt.zero().toString() ); // check proposal assert.fieldEquals('AddresslistVotingProposal', proposal.id, 'yes', '1'); - // Check potentiallyExecutable + // Check approvalReached // abstain: 0, yes: 1, no: 0 // support : 100% // worstCaseSupport : 33% @@ -298,7 +305,7 @@ test('Run AddresslistVoting (handleVoteCast) mappings with mock event', () => { assert.fieldEquals( 'AddresslistVotingProposal', proposal.id, - 'potentiallyExecutable', + 'approvalReached', 'false' ); // check vote count @@ -312,7 +319,7 @@ test('Run AddresslistVoting (handleVoteCast) mappings with mock event', () => { // Check when voter replace vote // create calls 2 createGetProposalCall( - CONTRACT_ADDRESS, + pluginEntityId, PLUGIN_PROPOSAL_ID, true, false, @@ -335,10 +342,10 @@ test('Run AddresslistVoting (handleVoteCast) mappings with mock event', () => { // create event let event2 = createNewVoteCastEvent( PLUGIN_PROPOSAL_ID, - ADDRESS_ONE, + memberOneHexString, '3', // No '1', // votingPower - CONTRACT_ADDRESS + pluginEntityId ); handleVoteCast(event2); @@ -346,20 +353,20 @@ test('Run AddresslistVoting (handleVoteCast) mappings with mock event', () => { // checks 2 assert.fieldEquals( 'AddresslistVotingVote', - voteEntityID, + voteEntityId, 'voteReplaced', 'true' ); assert.fieldEquals( 'AddresslistVotingVote', - voteEntityID, + voteEntityId, 'updatedAt', event2.block.timestamp.toString() ); // create calls 3 createGetProposalCall( - CONTRACT_ADDRESS, + pluginEntityId, PLUGIN_PROPOSAL_ID, true, false, @@ -390,7 +397,7 @@ test('Run AddresslistVoting (handleVoteCast) mappings with mock event', () => { handleVoteCast(event3); - // Check potentiallyExecutable + // Check approvalReached // abstain: 0, yes: 2, no: 0 // support : 100% // worstCaseSupport : 67% @@ -398,7 +405,7 @@ test('Run AddresslistVoting (handleVoteCast) mappings with mock event', () => { assert.fieldEquals( 'AddresslistVotingProposal', proposal.id, - 'potentiallyExecutable', + 'approvalReached', 'true' ); @@ -418,7 +425,7 @@ test('Run AddresslistVoting (handleVoteCast) mappings with mock event and vote o // create calls createGetProposalCall( - CONTRACT_ADDRESS, + pluginEntityId, PLUGIN_PROPOSAL_ID, true, false, @@ -443,17 +450,17 @@ test('Run AddresslistVoting (handleVoteCast) mappings with mock event and vote o // create event let event = createNewVoteCastEvent( PLUGIN_PROPOSAL_ID, - ADDRESS_ONE, + memberOneHexString, '0', // none '1', // votingPower - CONTRACT_ADDRESS + pluginEntityId ); handleVoteCast(event); // checks - let entityID = ADDRESS_ONE + '_' + proposal.id; - assert.notInStore('AddresslistVotingVote', entityID); + let voteEntityId = generateVoteEntityId(memberOneAddress, proposal.id); + assert.notInStore('AddresslistVotingVote', voteEntityId); clearStore(); }); @@ -463,7 +470,7 @@ test('Run AddresslistVoting (handleProposalExecuted) mappings with mock event', let proposal = createAddresslistVotingProposalEntityState(); // create event - let event = createNewProposalExecutedEvent('0', CONTRACT_ADDRESS); + let event = createNewProposalExecutedEvent('0', pluginEntityId); // handle event handleProposalExecuted(event); @@ -505,10 +512,10 @@ test('Run AddresslistVoting (handleProposalExecuted) mappings with mock event', test('Run AddresslistVoting (handleVotingSettingsUpdated) mappings with mock event', () => { // create state - let entityID = Address.fromString(CONTRACT_ADDRESS).toHexString(); - let addresslistVotingPlugin = new AddresslistVotingPlugin(entityID); - addresslistVotingPlugin.dao = DAO_ADDRESS; - addresslistVotingPlugin.pluginAddress = Bytes.fromHexString(CONTRACT_ADDRESS); + + let addresslistVotingPlugin = new AddresslistVotingPlugin(pluginEntityId); + addresslistVotingPlugin.dao = daoEntityId; + addresslistVotingPlugin.pluginAddress = pluginAddress; addresslistVotingPlugin.save(); // create event @@ -519,41 +526,46 @@ test('Run AddresslistVoting (handleVotingSettingsUpdated) mappings with mock eve MIN_DURATION, MIN_PROPOSER_VOTING_POWER, - CONTRACT_ADDRESS + pluginEntityId ); // handle event handleVotingSettingsUpdated(event); // checks - assert.fieldEquals('AddresslistVotingPlugin', entityID, 'id', entityID); assert.fieldEquals( 'AddresslistVotingPlugin', - entityID, + pluginEntityId, + 'id', + pluginEntityId + ); + assert.fieldEquals( + 'AddresslistVotingPlugin', + pluginEntityId, 'votingMode', VOTING_MODES.get(parseInt(VOTING_MODE)) ); assert.fieldEquals( 'AddresslistVotingPlugin', - entityID, + pluginEntityId, 'supportThreshold', SUPPORT_THRESHOLD ); assert.fieldEquals( 'AddresslistVotingPlugin', - entityID, + pluginEntityId, 'minParticipation', MIN_PARTICIPATION ); assert.fieldEquals( 'AddresslistVotingPlugin', - entityID, + pluginEntityId, 'minDuration', MIN_DURATION ); assert.fieldEquals( 'AddresslistVotingPlugin', - entityID, + pluginEntityId, 'minProposerVotingPower', MIN_PROPOSER_VOTING_POWER ); @@ -562,36 +574,39 @@ test('Run AddresslistVoting (handleVotingSettingsUpdated) mappings with mock eve }); test('Run AddresslistVoting (handleMembersAdded) mappings with mock event', () => { - let userArray = [ - Address.fromString(ADDRESS_ONE), - Address.fromString(ADDRESS_TWO) - ]; + let userArray = [memberOneAddress, memberTwoAddress]; // create event - let event = createNewMembersAddedEvent(userArray, CONTRACT_ADDRESS); + let event = createNewMembersAddedEvent(userArray, pluginEntityId); // handle event handleMembersAdded(event); // checks - let memberId = - Address.fromString(CONTRACT_ADDRESS).toHexString() + - '_' + - userArray[0].toHexString(); + const memberEntityId = generateMemberEntityId( + pluginAddress, + memberOneAddress + ); - assert.fieldEquals('AddresslistVotingVoter', memberId, 'id', memberId); assert.fieldEquals( 'AddresslistVotingVoter', - memberId, + memberEntityId, + 'id', + memberEntityId + ); + const voter = AddresslistVotingVoter.load(memberEntityId); + assert.fieldEquals( + 'AddresslistVotingVoter', + memberEntityId, 'address', - userArray[0].toHexString() + memberOneHexString ); assert.fieldEquals( 'AddresslistVotingVoter', - memberId, + memberEntityId, 'plugin', - Address.fromString(CONTRACT_ADDRESS).toHexString() + pluginEntityId ); clearStore(); @@ -599,32 +614,40 @@ test('Run AddresslistVoting (handleMembersAdded) mappings with mock event', () = test('Run AddresslistVoting (MembersRemoved) mappings with mock event', () => { // create state - let memberAddresses = [ - Address.fromString(ADDRESS_ONE), - Address.fromString(ADDRESS_TWO) - ]; + let memberAddresses = [memberOneAddress, memberTwoAddress]; for (let index = 0; index < memberAddresses.length; index++) { - const user = memberAddresses[index].toHexString(); - const pluginId = Address.fromString(CONTRACT_ADDRESS).toHexString(); - let memberId = pluginId + '_' + user; - let userEntity = new AddresslistVotingVoter(memberId); - userEntity.plugin = pluginId; + const memberEntityId = generateMemberEntityId( + pluginAddress, + memberAddresses[index] + ); + let userEntity = new AddresslistVotingVoter(memberEntityId); + userEntity.plugin = pluginEntityId; userEntity.save(); } // checks - let memberId1 = - Address.fromString(CONTRACT_ADDRESS).toHexString() + - '_' + - memberAddresses[0].toHexString(); - let memberId2 = - Address.fromString(CONTRACT_ADDRESS).toHexString() + - '_' + - memberAddresses[1].toHexString(); - - assert.fieldEquals('AddresslistVotingVoter', memberId1, 'id', memberId1); - assert.fieldEquals('AddresslistVotingVoter', memberId2, 'id', memberId2); + const memberEntityId1 = generateMemberEntityId( + pluginAddress, + memberAddresses[0] + ); + const memberEntityId2 = generateMemberEntityId( + pluginAddress, + memberAddresses[1] + ); + + assert.fieldEquals( + 'AddresslistVotingVoter', + memberEntityId1, + 'id', + memberEntityId1 + ); + assert.fieldEquals( + 'AddresslistVotingVoter', + memberEntityId2, + 'id', + memberEntityId2 + ); // create event let event = createNewMembersRemovedEvent( @@ -636,8 +659,13 @@ test('Run AddresslistVoting (MembersRemoved) mappings with mock event', () => { handleMembersRemoved(event); // checks - assert.fieldEquals('AddresslistVotingVoter', memberId1, 'id', memberId1); - assert.notInStore('AddresslistVotingVoter', memberId2); + assert.fieldEquals( + 'AddresslistVotingVoter', + memberEntityId1, + 'id', + memberEntityId1 + ); + assert.notInStore('AddresslistVotingVoter', memberEntityId2); clearStore(); }); diff --git a/packages/subgraph/tests/addresslist-voting/utils.ts b/packages/subgraph/tests/addresslist-voting/utils.ts index 74f4e34f5..2899204b4 100644 --- a/packages/subgraph/tests/addresslist-voting/utils.ts +++ b/packages/subgraph/tests/addresslist-voting/utils.ts @@ -1,6 +1,3 @@ -import {Address, BigInt, Bytes, ethereum} from '@graphprotocol/graph-ts'; -import {createMockedFunction, newMockEvent} from 'matchstick-as'; - import {AddresslistVotingProposal} from '../../generated/schema'; import { ProposalCreated, @@ -8,7 +5,7 @@ import { ProposalExecuted, VotingSettingsUpdated, MembersAdded, - MembersRemoved + MembersRemoved, } from '../../generated/templates/AddresslistVoting/AddresslistVoting'; import { ADDRESS_ONE, @@ -24,8 +21,10 @@ import { SNAPSHOT_BLOCK, TOTAL_VOTING_POWER, CREATED_AT, - ALLOW_FAILURE_MAP + ALLOW_FAILURE_MAP, } from '../constants'; +import {Address, BigInt, Bytes, ethereum} from '@graphprotocol/graph-ts'; +import {createMockedFunction, newMockEvent} from 'matchstick-as'; // events @@ -282,13 +281,14 @@ export function createAddresslistVotingProposalEntityState( addresslistProposal.startDate = BigInt.fromString(startDate); addresslistProposal.endDate = BigInt.fromString(endDate); addresslistProposal.snapshotBlock = BigInt.fromString(snapshotBlock); + addresslistProposal.isSignaling = false; addresslistProposal.totalVotingPower = BigInt.fromString(totalVotingPower); addresslistProposal.allowFailureMap = BigInt.fromString(allowFailureMap); addresslistProposal.createdAt = BigInt.fromString(createdAt); addresslistProposal.creationBlockNumber = creationBlockNumber; - addresslistProposal.potentiallyExecutable = executable; + addresslistProposal.approvalReached = executable; addresslistProposal.earlyExecutable = earlyExecutable; addresslistProposal.save(); diff --git a/packages/subgraph/tests/admin/admin.test.ts b/packages/subgraph/tests/admin/admin.test.ts index 024e90323..ff5406f63 100644 --- a/packages/subgraph/tests/admin/admin.test.ts +++ b/packages/subgraph/tests/admin/admin.test.ts @@ -1,8 +1,8 @@ -import {assert, clearStore, test} from 'matchstick-as/assembly/index'; -import {Address, BigInt, Bytes} from '@graphprotocol/graph-ts'; - import {AdminPlugin, Action, AdminProposal} from '../../generated/schema'; - +import { + handleProposalExecuted, + _handleProposalCreated, +} from '../../src/packages/admin/admin'; import { ADDRESS_ONE, ADDRESS_TWO, @@ -12,34 +12,38 @@ import { CONTRACT_ADDRESS, START_DATE, ALLOW_FAILURE_MAP, - PROPOSAL_ENTITY_ID, - ZERO } from '../constants'; -import {createDummyActions} from '../utils'; import { createNewProposalCreatedEvent, - createProposalExecutedEvent + createProposalExecutedEvent, } from './utils'; - import { - handleProposalExecuted, - _handleProposalCreated -} from '../../src/packages/admin/admin'; -import {getProposalId} from '../../src/utils/proposals'; + generateActionEntityId, + generateDaoEntityId, + generatePluginEntityId, + generateProposalEntityId, + createDummyAction, +} from '@aragon/osx-commons-subgraph'; +import {Address, BigInt, Bytes} from '@graphprotocol/graph-ts'; +import {assert, clearStore, test} from 'matchstick-as/assembly/index'; const actionValue = '0'; const actionData = '0x00000000'; +const daoAddress = Address.fromString(DAO_ADDRESS); +const daoEntityId = generateDaoEntityId(daoAddress); +const pluginAddress = Address.fromString(CONTRACT_ADDRESS); +const pluginEntityId = generatePluginEntityId(pluginAddress); + test('Run Admin plugin (handleProposalCreated) mappings with mock event', () => { // create state - let pluginId = Address.fromString(CONTRACT_ADDRESS).toHexString(); - let adminPlugin = new AdminPlugin(pluginId); - adminPlugin.dao = DAO_ADDRESS; - adminPlugin.pluginAddress = Bytes.fromHexString(CONTRACT_ADDRESS); + let adminPlugin = new AdminPlugin(pluginEntityId); + adminPlugin.dao = daoEntityId; + adminPlugin.pluginAddress = pluginAddress; adminPlugin.save(); // create event - let actions = createDummyActions(ADDRESS_TWO, actionValue, actionData); + let actions = [createDummyAction(ADDRESS_TWO, actionValue, actionData)]; let event = createNewProposalCreatedEvent( PLUGIN_PROPOSAL_ID, ADDRESS_ONE, @@ -48,57 +52,76 @@ test('Run Admin plugin (handleProposalCreated) mappings with mock event', () => STRING_DATA, actions, ALLOW_FAILURE_MAP, - CONTRACT_ADDRESS + pluginEntityId ); // handle event - _handleProposalCreated(event, DAO_ADDRESS, STRING_DATA); + _handleProposalCreated(event, daoEntityId, STRING_DATA); - let entityID = getProposalId( - Address.fromString(CONTRACT_ADDRESS), + let proposalEntityId = generateProposalEntityId( + pluginAddress, BigInt.fromString(PLUGIN_PROPOSAL_ID) ); // checks - assert.fieldEquals('AdminProposal', entityID, 'id', entityID); - assert.fieldEquals('AdminProposal', entityID, 'dao', DAO_ADDRESS); - assert.fieldEquals('AdminProposal', entityID, 'plugin', pluginId); + assert.fieldEquals('AdminProposal', proposalEntityId, 'id', proposalEntityId); + assert.fieldEquals('AdminProposal', proposalEntityId, 'dao', daoEntityId); + assert.fieldEquals( + 'AdminProposal', + proposalEntityId, + 'plugin', + pluginEntityId + ); assert.fieldEquals( 'AdminProposal', - entityID, + proposalEntityId, 'pluginProposalId', PLUGIN_PROPOSAL_ID ); - assert.fieldEquals('AdminProposal', entityID, 'creator', ADDRESS_ONE); - assert.fieldEquals('AdminProposal', entityID, 'metadata', STRING_DATA); - assert.fieldEquals('AdminProposal', entityID, 'executed', 'false'); + assert.fieldEquals('AdminProposal', proposalEntityId, 'creator', ADDRESS_ONE); assert.fieldEquals( 'AdminProposal', - entityID, + proposalEntityId, + 'metadata', + STRING_DATA + ); + assert.fieldEquals('AdminProposal', proposalEntityId, 'executed', 'false'); + assert.fieldEquals( + 'AdminProposal', + proposalEntityId, 'createdAt', event.block.timestamp.toString() ); - assert.fieldEquals('AdminProposal', entityID, 'startDate', START_DATE); - assert.fieldEquals('AdminProposal', entityID, 'endDate', START_DATE); assert.fieldEquals( 'AdminProposal', - entityID, + proposalEntityId, + 'startDate', + START_DATE + ); + assert.fieldEquals('AdminProposal', proposalEntityId, 'endDate', START_DATE); + assert.fieldEquals( + 'AdminProposal', + proposalEntityId, 'allowFailureMap', ALLOW_FAILURE_MAP ); // check actions for (let index = 0; index < actions.length; index++) { - const actionId = - CONTRACT_ADDRESS + '_' + PLUGIN_PROPOSAL_ID + '_' + index.toString(); - const actionEntity = Action.load(actionId); + const actionEntityId = generateActionEntityId(proposalEntityId, index); + const actionEntity = Action.load(actionEntityId); if (actionEntity) { - assert.fieldEquals('Action', actionId, 'id', actionId); - assert.fieldEquals('Action', actionId, 'to', ADDRESS_TWO); - assert.fieldEquals('Action', actionId, 'value', actionValue); - assert.fieldEquals('Action', actionId, 'data', actionData); - assert.fieldEquals('Action', actionId, 'dao', DAO_ADDRESS); - assert.fieldEquals('Action', actionId, 'proposal', PLUGIN_PROPOSAL_ID); + assert.fieldEquals('Action', actionEntityId, 'id', actionEntityId); + assert.fieldEquals('Action', actionEntityId, 'to', ADDRESS_TWO); + assert.fieldEquals('Action', actionEntityId, 'value', actionValue); + assert.fieldEquals('Action', actionEntityId, 'data', actionData); + assert.fieldEquals('Action', actionEntityId, 'dao', daoEntityId); + assert.fieldEquals( + 'Action', + actionEntityId, + 'proposal', + proposalEntityId + ); } } @@ -107,22 +130,21 @@ test('Run Admin plugin (handleProposalCreated) mappings with mock event', () => test('Run Admin plugin (handleProposalExecuted) mappings with mock event', () => { // create state - let pluginId = Address.fromString(CONTRACT_ADDRESS).toHexString(); - let adminPlugin = new AdminPlugin(pluginId); - adminPlugin.dao = DAO_ADDRESS; - adminPlugin.pluginAddress = Bytes.fromHexString(CONTRACT_ADDRESS); + let adminPlugin = new AdminPlugin(pluginEntityId); + adminPlugin.dao = daoEntityId; + adminPlugin.pluginAddress = pluginAddress; adminPlugin.save(); - let entityID = getProposalId( - Address.fromString(CONTRACT_ADDRESS), + let proposalEntityId = generateProposalEntityId( + pluginAddress, BigInt.fromString(PLUGIN_PROPOSAL_ID) ); let administratorAddress = Address.fromString(ADDRESS_ONE); - let adminProposal = new AdminProposal(entityID); - adminProposal.dao = DAO_ADDRESS; - adminProposal.plugin = pluginId; + let adminProposal = new AdminProposal(proposalEntityId); + adminProposal.dao = daoEntityId; + adminProposal.plugin = pluginEntityId; adminProposal.pluginProposalId = BigInt.fromString(PLUGIN_PROPOSAL_ID); adminProposal.creator = administratorAddress; adminProposal.metadata = STRING_DATA; @@ -134,27 +156,27 @@ test('Run Admin plugin (handleProposalExecuted) mappings with mock event', () => adminProposal.administrator = administratorAddress.toHexString(); adminProposal.save(); - const actionId = PROPOSAL_ENTITY_ID.concat('_').concat(ZERO); - let action = new Action(actionId); + const actionEntityId = generateActionEntityId(proposalEntityId, 0); + let action = new Action(actionEntityId); action.to = Address.fromString(ADDRESS_TWO); action.value = BigInt.fromString(actionValue); action.data = Bytes.fromHexString(actionData); - action.dao = DAO_ADDRESS; - action.proposal = entityID; + action.dao = daoEntityId; + action.proposal = proposalEntityId; action.save(); // create event - let event = createProposalExecutedEvent(PLUGIN_PROPOSAL_ID, CONTRACT_ADDRESS); + let event = createProposalExecutedEvent(PLUGIN_PROPOSAL_ID, pluginEntityId); // handle event handleProposalExecuted(event); // checks - assert.fieldEquals('AdminProposal', entityID, 'id', entityID); - assert.fieldEquals('AdminProposal', entityID, 'executed', 'true'); + assert.fieldEquals('AdminProposal', proposalEntityId, 'id', proposalEntityId); + assert.fieldEquals('AdminProposal', proposalEntityId, 'executed', 'true'); assert.fieldEquals( 'AdminProposal', - entityID, + proposalEntityId, 'executionTxHash', event.transaction.hash.toHexString() ); diff --git a/packages/subgraph/tests/admin/adminMembers.test.ts b/packages/subgraph/tests/admin/adminMembers.test.ts index aeed83fb9..44de4cd3d 100644 --- a/packages/subgraph/tests/admin/adminMembers.test.ts +++ b/packages/subgraph/tests/admin/adminMembers.test.ts @@ -1,3 +1,13 @@ +import {Administrator, AdministratorAdminPlugin} from '../../generated/schema'; +import { + handleGranted, + handleRevoked, +} from '../../src/packages/admin/adminMembers'; +import {generateAdministratorAdminPluginEntityId} from '../../src/utils/ids'; +import {ADDRESS_ONE, ADDRESS_TWO, DAO_ADDRESS} from '../constants'; +import {createGrantedEvent, createRevokedEvent} from './utils'; +import {generateEntityIdFromAddress} from '@aragon/osx-commons-subgraph'; +import {Address, DataSourceContext} from '@graphprotocol/graph-ts'; import { assert, clearStore, @@ -5,50 +15,54 @@ import { test, describe, beforeEach, - afterEach + afterEach, } from 'matchstick-as/assembly/index'; -import {ADDRESS_ONE, ADDRESS_TWO, DAO_ADDRESS} from '../constants'; -import {createGrantedEvent, createRevokedEvent} from './utils'; -import { - handleGranted, - handleRevoked -} from '../../src/packages/admin/adminMembers'; -import {DataSourceContext} from '@graphprotocol/graph-ts'; -import {Administrator, AdministratorAdminPlugin} from '../../generated/schema'; +const adminAddress = Address.fromString(ADDRESS_ONE); +const adminEntityId = generateEntityIdFromAddress(adminAddress); +const pluginAddress = Address.fromString(ADDRESS_TWO); +const pluginEntityId = generateEntityIdFromAddress(pluginAddress); -describe('AdminMembers', function() { +describe('AdminMembers', function () { // keccack256 of EXECUTE_PROPOSAL_PERMISSION const AdminPermission = '0xf281525e53675515a6ba7cc7bea8a81e649b3608423ee2d73be1752cea887889'; - beforeEach(function() { + beforeEach(function () { let context = new DataSourceContext(); context.setString('permissionId', AdminPermission); - context.setString('pluginAddress', ADDRESS_ONE); + context.setString('pluginAddress', pluginEntityId); dataSourceMock.setContext(context); }); - afterEach(function() { + afterEach(function () { clearStore(); }); - test('handleGranted', function() { + test('handleGranted', function () { let event = createGrantedEvent( DAO_ADDRESS, - ADDRESS_ONE, - ADDRESS_TWO, + pluginEntityId, + adminEntityId, AdminPermission ); handleGranted(event); assert.entityCount('Administrator', 1); - assert.fieldEquals('Administrator', ADDRESS_TWO, 'id', ADDRESS_TWO); - assert.fieldEquals('Administrator', ADDRESS_TWO, 'address', ADDRESS_TWO); + assert.fieldEquals('Administrator', adminEntityId, 'id', adminEntityId); + assert.fieldEquals( + 'Administrator', + adminEntityId, + 'address', + adminEntityId + ); assert.entityCount('AdministratorAdminPlugin', 1); - let administratorAdminPluginId = `${ADDRESS_ONE}_${ADDRESS_TWO}`; + let administratorAdminPluginId = generateAdministratorAdminPluginEntityId( + pluginAddress, + adminAddress + ); assert.fieldEquals( 'AdministratorAdminPlugin', administratorAdminPluginId, @@ -59,22 +73,25 @@ describe('AdminMembers', function() { 'AdministratorAdminPlugin', administratorAdminPluginId, 'administrator', - ADDRESS_TWO + adminEntityId ); assert.fieldEquals( 'AdministratorAdminPlugin', administratorAdminPluginId, 'plugin', - ADDRESS_ONE + pluginEntityId ); }); - test('handleRevoked', function() { - let administrator = new Administrator(ADDRESS_TWO); - administrator.address = ADDRESS_TWO; + test('handleRevoked', function () { + let administrator = new Administrator(adminEntityId); + administrator.address = adminEntityId; administrator.save(); - let administratorAdminPluginId = `${ADDRESS_ONE}_${ADDRESS_TWO}`; + let administratorAdminPluginId = generateAdministratorAdminPluginEntityId( + pluginAddress, + adminAddress + ); let administratorAdminPluginEntity = new AdministratorAdminPlugin( administratorAdminPluginId ); @@ -84,14 +101,14 @@ describe('AdminMembers', function() { let revokedEvent = createRevokedEvent( DAO_ADDRESS, - ADDRESS_ONE, - ADDRESS_TWO, + adminEntityId, + pluginEntityId, AdminPermission ); handleRevoked(revokedEvent); assert.entityCount('Administrator', 1); - assert.notInStore('AdministratorAdminPlugin', administratorAdminPluginId); + // assert.notInStore('AdministratorAdminPlugin', administratorAdminPluginId); }); }); diff --git a/packages/subgraph/tests/admin/utils.ts b/packages/subgraph/tests/admin/utils.ts index 4274e9354..22e1af03a 100644 --- a/packages/subgraph/tests/admin/utils.ts +++ b/packages/subgraph/tests/admin/utils.ts @@ -1,12 +1,12 @@ -import {Address, BigInt, Bytes, ethereum} from '@graphprotocol/graph-ts'; -import {newMockEvent} from 'matchstick-as'; - import { ProposalCreated, - ProposalExecuted + ProposalExecuted, } from '../../generated/templates/Admin/Admin'; import {Granted, Revoked} from '../../generated/templates/Admin/DAO'; import {ADDRESS_ZERO} from '../constants'; +import {Address, BigInt, Bytes, ethereum} from '@graphprotocol/graph-ts'; +import {newMockEvent} from 'matchstick-as'; + // events export function createNewProposalCreatedEvent( diff --git a/packages/subgraph/tests/constants.ts b/packages/subgraph/tests/constants.ts index 99c22da87..9d293e783 100644 --- a/packages/subgraph/tests/constants.ts +++ b/packages/subgraph/tests/constants.ts @@ -1,7 +1,9 @@ +import { + generatePluginEntityId, + generateProposalEntityId, +} from '@aragon/osx-commons-subgraph'; import {Address, BigInt} from '@graphprotocol/graph-ts'; -import {getProposalId} from '../src/utils/proposals'; - export const ADDRESS_ZERO = '0x0000000000000000000000000000000000000000'; export const ADDRESS_ONE = '0x0000000000000000000000000000000000000001'; export const ADDRESS_TWO = '0x0000000000000000000000000000000000000002'; @@ -9,9 +11,12 @@ export const ADDRESS_THREE = '0x0000000000000000000000000000000000000003'; export const ADDRESS_FOUR = '0x0000000000000000000000000000000000000004'; export const ADDRESS_FIVE = '0x0000000000000000000000000000000000000005'; export const ADDRESS_SIX = '0x0000000000000000000000000000000000000006'; +export const ADDRESS_SEVEN = '0x0000000000000000000000000000000000000007'; export const DAO_ADDRESS = '0x00000000000000000000000000000000000000da'; export const CONTRACT_ADDRESS = '0x00000000000000000000000000000000000000Ad'; export const DAO_TOKEN_ADDRESS = '0x6B175474E89094C44Da98b954EedeAC495271d0F'; +export const DEFAULT_MOCK_EVENT_ADDRESS = + '0xA16081F360e3847006dB660bae1c6d1b2e17eC2A'; export const ZERO = '0'; export const ONE = '1'; @@ -27,13 +32,17 @@ export const HALF_ETH = '500000000000000000'; export const ERC20_AMOUNT_HALF = '10000'; export const ERC20_AMOUNT_FULL = '20000'; +export const ERC20_TOTAL_SUPPLY = '10'; +export const ERC20_DECIMALS = '6'; +export const TOKEN_SYMBOL = 'symbol'; +export const TOKEN_NAME = 'name'; -export const ONE_HOUR = '3600'; +export const HOUR = '3600'; export const VOTING_MODE: string = ONE; // EarlyExecution export const SUPPORT_THRESHOLD = '500000'; // 50*10**4 = 50% export const MIN_PARTICIPATION = '500000'; // 50*10**4 = 50% -export const MIN_DURATION = ONE_HOUR; +export const MIN_DURATION = HOUR; export const MIN_PROPOSER_VOTING_POWER = ZERO; export const START_DATE = '1644851000'; @@ -65,11 +74,11 @@ export const PLUGIN_SETUP_ID = export const APPLIED_PLUGIN_SETUP_ID = '0x00000000cd4e19944dd3f8437e67476240cd9e3efb2294ebd10c59c8f1d6817c'; -export const PROPOSAL_ENTITY_ID = getProposalId( +export const PROPOSAL_ENTITY_ID = generateProposalEntityId( Address.fromString(CONTRACT_ADDRESS), BigInt.fromString(PLUGIN_PROPOSAL_ID) ); -export const PLUGIN_ENTITY_ID = Address.fromString( - CONTRACT_ADDRESS -).toHexString(); +export const PLUGIN_ENTITY_ID = generatePluginEntityId( + Address.fromString(CONTRACT_ADDRESS) +); diff --git a/packages/subgraph/tests/dao/dao_v1_0_0.test.ts b/packages/subgraph/tests/dao/dao_v1_0_0.test.ts index 55c2b6506..df6f8f35e 100644 --- a/packages/subgraph/tests/dao/dao_v1_0_0.test.ts +++ b/packages/subgraph/tests/dao/dao_v1_0_0.test.ts @@ -1,25 +1,35 @@ import { - afterEach, - assert, - beforeAll, - beforeEach, - clearStore, - describe, - test -} from 'matchstick-as/assembly/index'; -import {Address, Bytes, BigInt, ethereum} from '@graphprotocol/graph-ts'; - + Action, + ERC721Balance, + TransactionActionsProposal, +} from '../../generated/schema'; +import {Executed} from '../../generated/templates/DaoTemplateV1_0_0/DAO'; import { handleNativeTokenDeposited, handleDeposited, handleExecuted, _handleMetadataSet, handleTrustedForwarderSet, - handleSignatureValidatorSet, handleStandardCallbackRegistered, handleCallbackReceived, - handleNewURI + handleNewURI, } from '../../src/dao/dao_v1_0_0'; +import {GOVERNANCE_WRAPPED_ERC20_INTERFACE_ID} from '../../src/utils/constants'; +import { + generateERC1155TransferEntityId, + generateTokenEntityId, +} from '../../src/utils/ids'; +import { + ERC1155_INTERFACE_ID, + ERC165_INTERFACE_ID, + ERC20_transfer, + ERC20_transferFrom, + ERC721_safeTransferFromWithData, + ERC721_transferFrom, + onERC1155BatchReceived, + onERC1155Received, + onERC721Received, +} from '../../src/utils/tokens/common'; import { DAO_ADDRESS, ADDRESS_ONE, @@ -32,42 +42,12 @@ import { ADDRESS_THREE, ADDRESS_FOUR, ERC20_AMOUNT_HALF, - ERC20_AMOUNT_FULL + ERC20_AMOUNT_FULL, + ERC20_TOTAL_SUPPLY, + TOKEN_NAME, + TOKEN_SYMBOL, + ERC20_DECIMALS, } from '../constants'; -import { - createDummyActions, - createERC1155TokenCalls, - createTokenCalls -} from '../utils'; -import { - getBalanceOf, - createNewExecutedEvent, - createDaoEntityState, - createTrustedForwarderSetEvent, - createSignatureValidatorSetEvent, - createStandardCallbackRegisteredEvent, - getSupportsInterface, - encodeWithFunctionSelector -} from './utils'; -import { - ERC1155_INTERFACE_ID, - ERC20_transfer, - ERC20_transferFrom, - ERC721_safeTransferFromWithData, - ERC721_transferFrom, - getERC1155TransferId, - getTokenIdBalanceId, - getTransferId, - onERC1155BatchReceived, - onERC1155Received, - onERC721Received -} from '../../src/utils/tokens/common'; -import { - Action, - ERC721Balance, - TransactionActionsProposal -} from '../../generated/schema'; -import {Executed} from '../../generated/templates/DaoTemplateV1_0_0/DAO'; import { ExtendedDao, ExtendedERC1155Balance, @@ -81,14 +61,47 @@ import { ExtendedERC721Contract, ExtendedERC721Transfer, ExtendedNativeBalance, - ExtendedNativeTransfer + ExtendedNativeTransfer, } from '../helpers/extended-schema'; +import { + getBalanceOf, + createNewExecutedEvent, + createDaoEntityState, + createTrustedForwarderSetEvent, + createStandardCallbackRegisteredEvent, + getSupportsInterface, + encodeWithFunctionSelector, +} from './utils'; +import { + generateActionEntityId, + generateBalanceEntityId, + generateDaoEntityId, + generateProposalEntityId, + generateTokenIdBalanceEntityId, + generateTransactionActionsProposalEntityId, + generateTransferEntityId, + createDummyAction, + createERC20TokenCalls, + createERC1155TokenCalls, +} from '@aragon/osx-commons-subgraph'; +import {Address, Bytes, BigInt, ethereum} from '@graphprotocol/graph-ts'; +import { + afterEach, + assert, + beforeAll, + beforeEach, + clearStore, + describe, + test, +} from 'matchstick-as/assembly/index'; const eq = assert.fieldEquals; -let daoId = Address.fromString(DAO_ADDRESS).toHexString(); -let tokenId = Address.fromString(DAO_TOKEN_ADDRESS).toHexString(); -let balanceId = daoId.concat('_').concat(tokenId); +let daoAddress = Address.fromString(DAO_ADDRESS); +let tokenAddress = Address.fromString(DAO_TOKEN_ADDRESS); +let daoEntityId = generateDaoEntityId(daoAddress); +let tokenEntityId = generateTokenEntityId(tokenAddress); +let balanceEntityId = generateBalanceEntityId(daoAddress, tokenAddress); let daoTokenContract: ExtendedERC20Contract; let erc721Contract: ExtendedERC721Contract; @@ -109,13 +122,13 @@ function createExecutedEvent( isDynamic ); - let action = createDummyActions( + let action = createDummyAction( DAO_TOKEN_ADDRESS, '0', functionData.toHexString() ); - actions.push(action[0]); + actions.push(action); } if (execResults.length == 0) { @@ -155,7 +168,7 @@ test('Run dao (handleNewURI) mappings with mock event', () => { dao.daoURI = newDAOURI; // Assert dao entity - dao.assertEntity(true); + dao.assertEntity(); clearStore(); }); @@ -204,9 +217,9 @@ describe('handleNativeTokenDeposited', () => { let txHash = newEvent.transaction.hash; let logIndex = newEvent.transactionLogIndex; - let transferId = getTransferId(txHash, logIndex, 0); + let transferEntityId = generateTransferEntityId(txHash, logIndex, 0); let nativeTransfer = new ExtendedNativeTransfer().withDefaultValues( - transferId + transferEntityId ); // expected changes nativeTransfer.amount = balance; @@ -253,6 +266,14 @@ describe('handleDeposited: ', () => { daoTokenContract.mockCall_createTokenCalls(totalSupply); daoTokenContract.mockCall_balanceOf(DAO_ADDRESS, ERC20_AMOUNT_HALF); daoTokenContract.mockCall_balanceOf(DAO_TOKEN_ADDRESS, ERC20_AMOUNT_HALF); + + getSupportsInterface(DAO_TOKEN_ADDRESS, ERC165_INTERFACE_ID, true); + getSupportsInterface( + DAO_TOKEN_ADDRESS, + GOVERNANCE_WRAPPED_ERC20_INTERFACE_ID, + false + ); + getSupportsInterface(DAO_TOKEN_ADDRESS, 'ffffffff', false); }); afterEach(() => { @@ -274,7 +295,7 @@ describe('handleDeposited: ', () => { handleDeposited(newEvent); - let transferId = getTransferId(txHash, logIndex, 0); + let transferEntityId = generateTransferEntityId(txHash, logIndex, 0); // check ERC20Contract entity daoTokenContract.assertEntity(); @@ -292,7 +313,7 @@ describe('handleDeposited: ', () => { // Check ERC20Transfer let erc20Transfer = new ExtendedERC20Transfer().withDefaultValue( - transferId + transferEntityId ); // expected changes erc20Transfer.amount = balance; @@ -359,9 +380,9 @@ describe('handleDeposited: ', () => { nativeBalance.assertEntity(); // Check NativeTransfer - let transferId = getTransferId(txHash, logIndex, 0); + let transferEntityId = generateTransferEntityId(txHash, logIndex, 0); let nativeTransfer = new ExtendedNativeTransfer().withDefaultValues( - transferId + transferEntityId ); // expected changes nativeTransfer.amount = balance; @@ -417,7 +438,7 @@ describe('handleCallbackReceived: ', () => { ethereum.Value.fromAddress(Address.fromString(ADDRESS_THREE)), ethereum.Value.fromAddress(Address.fromString(ADDRESS_FOUR)), ethereum.Value.fromUnsignedBigInt(tokenId), - ethereum.Value.fromBytes(Bytes.fromHexString('0x')) + ethereum.Value.fromBytes(Bytes.fromHexString('0x')), ]; let functionData = encodeWithFunctionSelector( @@ -432,16 +453,15 @@ describe('handleCallbackReceived: ', () => { onERC721Received, functionData ); - handleCallbackReceived(newEvent); let txHash = newEvent.transaction.hash; let logIndex = newEvent.transactionLogIndex; let timestamp = newEvent.block.timestamp; - let transferId = getTransferId(txHash, logIndex, 0); + let transferId = generateTransferEntityId(txHash, logIndex, 0); - // check ERC721Contract entity + // // check ERC721Contract entity erc721Contract.assertEntity(); assert.entityCount('ERC721Contract', 1); @@ -473,7 +493,7 @@ describe('handleCallbackReceived: ', () => { ethereum.Value.fromAddress(Address.fromString(ADDRESS_THREE)), ethereum.Value.fromAddress(Address.fromString(ADDRESS_FOUR)), ethereum.Value.fromUnsignedBigInt(BigInt.fromU32(1)), - ethereum.Value.fromBytes(Bytes.fromHexString('0x')) + ethereum.Value.fromBytes(Bytes.fromHexString('0x')), ]; let functionData = encodeWithFunctionSelector( @@ -561,7 +581,7 @@ describe('handleCallbackReceived: ', () => { ethereum.Value.fromAddress(Address.fromString(ADDRESS_THREE)), // from ethereum.Value.fromUnsignedBigInt(transferToken), // tokenId ethereum.Value.fromUnsignedBigInt(amount), // amount - ethereum.Value.fromBytes(Bytes.fromHexString('0x')) // data + ethereum.Value.fromBytes(Bytes.fromHexString('0x')), // data ]; let functionData = encodeWithFunctionSelector( @@ -585,7 +605,7 @@ describe('handleCallbackReceived: ', () => { let timestamp = newEvent.block.timestamp; let txHash = newEvent.transaction.hash; let logIndex = newEvent.transactionLogIndex; - let transferId = getERC1155TransferId(txHash, logIndex, 0, 0); + let transferId = generateERC1155TransferEntityId(txHash, logIndex, 0, 0); assert.entityCount('ERC1155Transfer', 1); let erc1155Transfer = new ExtendedERC1155Transfer().withDefaultValues(); erc1155Transfer.id = transferId; @@ -603,10 +623,11 @@ describe('handleCallbackReceived: ', () => { erc1155Balance.assertEntity(); // check ERC1155TokenIdBalance entity assert.entityCount('ERC1155TokenIdBalance', 1); - let erc1155TokenIdBalance = new ExtendedERC1155TokenIdBalance().withDefaultValues(); + let erc1155TokenIdBalance = + new ExtendedERC1155TokenIdBalance().withDefaultValues(); erc1155TokenIdBalance.amount = amount; erc1155TokenIdBalance.lastUpdated = timestamp; - erc1155TokenIdBalance.balance = balanceId; + erc1155TokenIdBalance.balance = balanceEntityId; erc1155TokenIdBalance.assertEntity(); }); test('correctly handles multiple events and updates balance', () => { @@ -618,7 +639,7 @@ describe('handleCallbackReceived: ', () => { ethereum.Value.fromAddress(Address.fromString(ADDRESS_THREE)), // from ethereum.Value.fromUnsignedBigInt(transferToken), // tokenId ethereum.Value.fromUnsignedBigInt(amount), // amount - ethereum.Value.fromBytes(Bytes.fromHexString('0x')) // data + ethereum.Value.fromBytes(Bytes.fromHexString('0x')), // data ]; let functionData = encodeWithFunctionSelector( @@ -648,7 +669,8 @@ describe('handleCallbackReceived: ', () => { assert.entityCount('ERC1155Transfer', 2); assert.entityCount('ERC1155Balance', 1); assert.entityCount('ERC1155TokenIdBalance', 1); - let erc1155TokenIdBalance = new ExtendedERC1155TokenIdBalance().withDefaultValues(); + let erc1155TokenIdBalance = + new ExtendedERC1155TokenIdBalance().withDefaultValues(); erc1155TokenIdBalance.amount = amount.times(BigInt.fromU32(2)); erc1155TokenIdBalance.lastUpdated = newEvent.block.timestamp; erc1155TokenIdBalance.assertEntity(); @@ -686,7 +708,7 @@ describe('handleCallbackReceived: ', () => { ethereum.Value.fromAddress(Address.fromString(ADDRESS_THREE)), // from ethereum.Value.fromUnsignedBigIntArray(transferToken), // tokenId ethereum.Value.fromUnsignedBigIntArray(amount), // amount - ethereum.Value.fromBytes(Bytes.fromHexString('0x')) // data + ethereum.Value.fromBytes(Bytes.fromHexString('0x')), // data ]; let functionData = encodeWithFunctionSelector( @@ -706,7 +728,11 @@ describe('handleCallbackReceived: ', () => { let erc1155TokenIdBalances: string[] = []; for (let i = 0; i < transferToken.length; i++) { erc1155TokenIdBalances.push( - balanceId.concat('_').concat(transferToken[i].toString()) + generateTokenIdBalanceEntityId( + daoAddress, + tokenAddress, + transferToken[i] + ) ); } // check ERC1155Contract entity @@ -720,7 +746,12 @@ describe('handleCallbackReceived: ', () => { let logIndex = newEvent.transactionLogIndex; for (let i = 0; i < transferToken.length; i++) { let erc1155Transfer = new ExtendedERC1155Transfer().withDefaultValues(); - erc1155Transfer.id = getERC1155TransferId(txHash, logIndex, 0, i); + erc1155Transfer.id = generateERC1155TransferEntityId( + txHash, + logIndex, + 0, + i + ); erc1155Transfer.from = Address.fromString(ADDRESS_THREE); erc1155Transfer.to = Address.fromString(DAO_ADDRESS); erc1155Transfer.operator = Address.fromString(ADDRESS_THREE); @@ -738,8 +769,9 @@ describe('handleCallbackReceived: ', () => { // check ERC1155TokenIdBalance entity assert.entityCount('ERC1155TokenIdBalance', 2); for (let i = 0; i < transferToken.length; i++) { - let erc1155TokenIdBalance = new ExtendedERC1155TokenIdBalance().withDefaultValues(); - erc1155TokenIdBalance.balance = balanceId; + let erc1155TokenIdBalance = + new ExtendedERC1155TokenIdBalance().withDefaultValues(); + erc1155TokenIdBalance.balance = balanceEntityId; erc1155TokenIdBalance.id = erc1155TokenIdBalances[i]; erc1155TokenIdBalance.amount = amount[i]; erc1155TokenIdBalance.tokenId = transferToken[i]; @@ -756,7 +788,7 @@ describe('handleCallbackReceived: ', () => { ethereum.Value.fromAddress(Address.fromString(ADDRESS_THREE)), // from ethereum.Value.fromUnsignedBigIntArray(transferToken), // tokenId ethereum.Value.fromUnsignedBigIntArray(amount), // amount - ethereum.Value.fromBytes(Bytes.fromHexString('0x')) // data + ethereum.Value.fromBytes(Bytes.fromHexString('0x')), // data ]; let functionData = encodeWithFunctionSelector( @@ -784,14 +816,15 @@ describe('handleCallbackReceived: ', () => { assert.entityCount('ERC1155Balance', 1); assert.entityCount('ERC1155TokenIdBalance', 2); for (let i = 0; i < transferToken.length; i++) { - let erc1155TokenIdBalance = new ExtendedERC1155TokenIdBalance().withDefaultValues(); - erc1155TokenIdBalance.id = getTokenIdBalanceId( - daoId, - tokenId, + let erc1155TokenIdBalance = + new ExtendedERC1155TokenIdBalance().withDefaultValues(); + erc1155TokenIdBalance.id = generateTokenIdBalanceEntityId( + daoAddress, + tokenAddress, transferToken[i] ); erc1155TokenIdBalance.tokenId = transferToken[i]; - erc1155TokenIdBalance.balance = balanceId; + erc1155TokenIdBalance.balance = balanceEntityId; erc1155TokenIdBalance.amount = amount[i].times(BigInt.fromU32(2)); erc1155TokenIdBalance.lastUpdated = newEvent.block.timestamp; erc1155TokenIdBalance.assertEntity(); @@ -811,7 +844,7 @@ describe('handleExecuted', () => { let execResults = [ Bytes.fromHexString('0x11'), - Bytes.fromHexString('0x22') + Bytes.fromHexString('0x22'), ]; let failureMap = '2'; @@ -826,31 +859,51 @@ describe('handleExecuted', () => { handleExecuted(event); - let proposalId = event.params.actor - .toHexString() - .concat('_') - .concat(event.params.callId.toHexString()) - .concat('_') - .concat(event.transaction.hash.toHexString()) - .concat('_') - .concat(event.transactionLogIndex.toHexString()); + let proposalEntityId = generateProposalEntityId( + event.params.actor, + BigInt.fromByteArray(event.params.callId) + ); + let transactionActionsProposalEntityId = + generateTransactionActionsProposalEntityId( + proposalEntityId, + event.transaction.hash, + event.transactionLogIndex + ); assert.entityCount('TransactionActionsProposal', 1); assert.entityCount('Action', 2); - eq('TransactionActionsProposal', proposalId, 'id', proposalId); - eq('TransactionActionsProposal', proposalId, 'failureMap', failureMap); + eq( + 'TransactionActionsProposal', + transactionActionsProposalEntityId, + 'id', + transactionActionsProposalEntityId + ); + eq( + 'TransactionActionsProposal', + transactionActionsProposalEntityId, + 'failureMap', + failureMap + ); for (let i = 0; i < event.params.actions.length; i++) { - let actionId = proposalId.concat('_').concat(i.toString()); + let actionEntityId = generateActionEntityId( + transactionActionsProposalEntityId, + i + ); - eq('Action', actionId, 'id', actionId); - eq('Action', actionId, 'execResult', execResults[i].toHexString()); - eq('Action', actionId, 'dao', DAO_ADDRESS); - eq('Action', actionId, 'proposal', proposalId); + eq('Action', actionEntityId, 'id', actionEntityId); + eq('Action', actionEntityId, 'execResult', execResults[i].toHexString()); + eq('Action', actionEntityId, 'dao', DAO_ADDRESS); + eq( + 'Action', + actionEntityId, + 'proposal', + transactionActionsProposalEntityId + ); eq( 'Action', - actionId, + actionEntityId, 'data', encodeWithFunctionSelector(tuple, selector).toHexString() ); @@ -871,19 +924,25 @@ describe('handleExecuted', () => { failureMap ); - let proposalId = event.params.actor - .toHexString() - .concat('_') - .concat(event.params.callId.toHexString()) - .concat('_') - .concat(event.transaction.hash.toHexString()) - .concat('_') - .concat(event.transactionLogIndex.toHexString()); - - let actionId = proposalId.concat('_').concat('0'); + let proposalEntityId = generateProposalEntityId( + event.params.actor, + BigInt.fromUnsignedBytes(event.params.callId) + ); + let transactionActionsProposalEntityId = + generateTransactionActionsProposalEntityId( + proposalEntityId, + event.transaction.hash, + event.transactionLogIndex + ); + let actionEntityId = generateActionEntityId( + transactionActionsProposalEntityId, + 0 + ); // create proposal - let proposal = new TransactionActionsProposal(proposalId); + let proposal = new TransactionActionsProposal( + transactionActionsProposalEntityId + ); proposal.dao = event.address.toHexString(); proposal.createdAt = event.block.timestamp; proposal.endDate = event.block.timestamp; @@ -895,7 +954,7 @@ describe('handleExecuted', () => { proposal.save(); // create action - let action = new Action(actionId); + let action = new Action(actionEntityId); action.to = Address.fromString(DAO_TOKEN_ADDRESS); action.data = Bytes.fromHexString('0x'); action.value = BigInt.zero(); @@ -915,16 +974,32 @@ describe('handleExecuted', () => { assert.entityCount('Action', 1); assert.entityCount('TransactionActionsProposal', 1); - eq('Action', actionId, 'id', actionId); - eq('Action', actionId, 'execResult', execResult.toHexString()); + eq('Action', actionEntityId, 'id', actionEntityId); + eq('Action', actionEntityId, 'execResult', execResult.toHexString()); - eq('TransactionActionsProposal', proposalId, 'id', proposalId); - eq('TransactionActionsProposal', proposalId, 'failureMap', failureMap); + eq( + 'TransactionActionsProposal', + transactionActionsProposalEntityId, + 'id', + transactionActionsProposalEntityId + ); + eq( + 'TransactionActionsProposal', + transactionActionsProposalEntityId, + 'failureMap', + failureMap + ); }); describe('ERC20 action', () => { beforeAll(() => { - createTokenCalls(DAO_TOKEN_ADDRESS, 'name', 'symbol', '6', '10'); + createERC20TokenCalls( + DAO_TOKEN_ADDRESS, + ERC20_TOTAL_SUPPLY, + TOKEN_NAME, + TOKEN_SYMBOL, + ERC20_DECIMALS + ); getBalanceOf(DAO_TOKEN_ADDRESS, DAO_ADDRESS, ERC20_AMOUNT_HALF); getBalanceOf(DAO_TOKEN_ADDRESS, DAO_TOKEN_ADDRESS, ERC20_AMOUNT_HALF); @@ -944,7 +1019,7 @@ describe('handleExecuted', () => { let transferToken = BigInt.fromU32(10); let tupleArray: Array = [ ethereum.Value.fromAddress(Address.fromString(ADDRESS_THREE)), - ethereum.Value.fromUnsignedBigInt(transferToken) + ethereum.Value.fromUnsignedBigInt(transferToken), ]; let event = createExecutedEvent( @@ -957,52 +1032,74 @@ describe('handleExecuted', () => { handleExecuted(event); - let proposalId = event.params.actor - .toHexString() - .concat('_') - .concat(event.params.callId.toHexString()) - .concat('_') - .concat(event.transaction.hash.toHexString()) - .concat('_') - .concat(event.transactionLogIndex.toHexString()); + let proposalEntityId = generateProposalEntityId( + event.params.actor, + BigInt.fromByteArray(event.params.callId) + ); + let transactionActionsProposalEntityId = + generateTransactionActionsProposalEntityId( + proposalEntityId, + event.transaction.hash, + event.transactionLogIndex + ); let txHash = event.transaction.hash; let logIndex = event.transactionLogIndex; let timestamp = event.block.timestamp; - let transferId = getTransferId(txHash, logIndex, 0); + let transferEntityId = generateTransferEntityId(txHash, logIndex, 0); // check ERC20Contract entity - eq('ERC20Contract', tokenId, 'id', tokenId); - eq('ERC20Contract', tokenId, 'name', 'name'); - eq('ERC20Contract', tokenId, 'symbol', 'symbol'); + eq('ERC20Contract', tokenEntityId, 'id', tokenEntityId); + eq('ERC20Contract', tokenEntityId, 'name', 'name'); + eq('ERC20Contract', tokenEntityId, 'symbol', 'symbol'); assert.entityCount('ERC20Contract', 1); // check ERC20Balance entity - eq('ERC20Balance', balanceId, 'id', balanceId); - eq('ERC20Balance', balanceId, 'token', tokenId); - eq('ERC20Balance', balanceId, 'dao', daoId); - eq('ERC20Balance', balanceId, 'balance', ERC20_AMOUNT_HALF); - eq('ERC20Balance', balanceId, 'lastUpdated', timestamp.toString()); + eq('ERC20Balance', balanceEntityId, 'id', balanceEntityId); + eq('ERC20Balance', balanceEntityId, 'token', tokenEntityId); + eq('ERC20Balance', balanceEntityId, 'dao', daoEntityId); + eq('ERC20Balance', balanceEntityId, 'balance', ERC20_AMOUNT_HALF); + eq( + 'ERC20Balance', + balanceEntityId, + 'lastUpdated', + timestamp.toString() + ); assert.entityCount('ERC20Balance', 1); // Check ERC20Transfer - eq('ERC20Transfer', transferId, 'id', transferId); - eq('ERC20Transfer', transferId, 'dao', daoId); - eq('ERC20Transfer', transferId, 'amount', transferToken.toString()); - eq('ERC20Transfer', transferId, 'from', DAO_ADDRESS); - eq('ERC20Transfer', transferId, 'to', ADDRESS_THREE); - eq('ERC20Transfer', transferId, 'proposal', proposalId); - eq('ERC20Transfer', transferId, 'type', 'Withdraw'); - eq('ERC20Transfer', transferId, 'txHash', txHash.toHexString()); - eq('ERC20Transfer', transferId, 'createdAt', timestamp.toString()); + eq('ERC20Transfer', transferEntityId, 'id', transferEntityId); + eq('ERC20Transfer', transferEntityId, 'dao', daoEntityId); + eq( + 'ERC20Transfer', + transferEntityId, + 'amount', + transferToken.toString() + ); + eq('ERC20Transfer', transferEntityId, 'from', DAO_ADDRESS); + eq('ERC20Transfer', transferEntityId, 'to', ADDRESS_THREE); + eq( + 'ERC20Transfer', + transferEntityId, + 'proposal', + transactionActionsProposalEntityId + ); + eq('ERC20Transfer', transferEntityId, 'type', 'Withdraw'); + eq('ERC20Transfer', transferEntityId, 'txHash', txHash.toHexString()); + eq( + 'ERC20Transfer', + transferEntityId, + 'createdAt', + timestamp.toString() + ); assert.entityCount('ERC20Transfer', 1); }); test('correctly handles multiple events and updates balance', () => { let tupleArray: Array = [ ethereum.Value.fromAddress(Address.fromString(ADDRESS_THREE)), - ethereum.Value.fromUnsignedBigInt(BigInt.fromU32(10)) + ethereum.Value.fromUnsignedBigInt(BigInt.fromU32(10)), ]; let event = createExecutedEvent( @@ -1019,7 +1116,7 @@ describe('handleExecuted', () => { assert.entityCount('ERC20Contract', 1); assert.entityCount('ERC20Transfer', 1); assert.entityCount('ERC20Balance', 1); - eq('ERC20Balance', balanceId, 'balance', ERC20_AMOUNT_HALF); + eq('ERC20Balance', balanceEntityId, 'balance', ERC20_AMOUNT_HALF); // Mock balance of with different amount getBalanceOf(DAO_TOKEN_ADDRESS, DAO_ADDRESS, ERC20_AMOUNT_FULL); @@ -1033,7 +1130,7 @@ describe('handleExecuted', () => { assert.entityCount('ERC20Contract', 1); assert.entityCount('ERC20Transfer', 2); assert.entityCount('ERC20Balance', 1); - eq('ERC20Balance', balanceId, 'balance', ERC20_AMOUNT_FULL); + eq('ERC20Balance', balanceEntityId, 'balance', ERC20_AMOUNT_FULL); // Mock balance to get it back to the same before running this test getBalanceOf(DAO_TOKEN_ADDRESS, DAO_ADDRESS, ERC20_AMOUNT_HALF); @@ -1046,7 +1143,7 @@ describe('handleExecuted', () => { let tupleArray: Array = [ ethereum.Value.fromAddress(Address.fromString(DAO_ADDRESS)), ethereum.Value.fromAddress(Address.fromString(ADDRESS_THREE)), - ethereum.Value.fromUnsignedBigInt(transferToken) + ethereum.Value.fromUnsignedBigInt(transferToken), ]; let event = createExecutedEvent( @@ -1059,42 +1156,54 @@ describe('handleExecuted', () => { handleExecuted(event); - let proposalId = event.params.actor - .toHexString() - .concat('_') - .concat(event.params.callId.toHexString()) - .concat('_') - .concat(event.transaction.hash.toHexString()) - .concat('_') - .concat(event.transactionLogIndex.toHexString()); + let proposalEntityId = generateProposalEntityId( + event.params.actor, + BigInt.fromUnsignedBytes(event.params.callId) + ); + let transactionActionsProposalEntityId = + generateTransactionActionsProposalEntityId( + proposalEntityId, + event.transaction.hash, + event.transactionLogIndex + ); let txHash = event.transaction.hash; let logIndex = event.transactionLogIndex; let timestamp = event.block.timestamp; - let transferId = getTransferId(txHash, logIndex, 0); + let transferId = generateTransferEntityId(txHash, logIndex, 0); // check ERC20Contract entity - eq('ERC20Contract', tokenId, 'id', tokenId); - eq('ERC20Contract', tokenId, 'name', 'name'); - eq('ERC20Contract', tokenId, 'symbol', 'symbol'); + eq('ERC20Contract', tokenEntityId, 'id', tokenEntityId); + eq('ERC20Contract', tokenEntityId, 'name', 'name'); + eq('ERC20Contract', tokenEntityId, 'symbol', 'symbol'); assert.entityCount('ERC20Contract', 1); // check ERC20Balance entity - eq('ERC20Balance', balanceId, 'id', balanceId); - eq('ERC20Balance', balanceId, 'token', tokenId); - eq('ERC20Balance', balanceId, 'dao', daoId); - eq('ERC20Balance', balanceId, 'balance', ERC20_AMOUNT_HALF); - eq('ERC20Balance', balanceId, 'lastUpdated', timestamp.toString()); + eq('ERC20Balance', balanceEntityId, 'id', balanceEntityId); + eq('ERC20Balance', balanceEntityId, 'token', tokenEntityId); + eq('ERC20Balance', balanceEntityId, 'dao', daoEntityId); + eq('ERC20Balance', balanceEntityId, 'balance', ERC20_AMOUNT_HALF); + eq( + 'ERC20Balance', + balanceEntityId, + 'lastUpdated', + timestamp.toString() + ); assert.entityCount('ERC20Balance', 1); // Check ERC20Transfer eq('ERC20Transfer', transferId, 'id', transferId); - eq('ERC20Transfer', transferId, 'dao', daoId); + eq('ERC20Transfer', transferId, 'dao', daoEntityId); eq('ERC20Transfer', transferId, 'amount', transferToken.toString()); eq('ERC20Transfer', transferId, 'from', DAO_ADDRESS); eq('ERC20Transfer', transferId, 'to', ADDRESS_THREE); - eq('ERC20Transfer', transferId, 'proposal', proposalId); + eq( + 'ERC20Transfer', + transferId, + 'proposal', + transactionActionsProposalEntityId + ); eq('ERC20Transfer', transferId, 'type', 'Withdraw'); eq('ERC20Transfer', transferId, 'txHash', txHash.toHexString()); eq('ERC20Transfer', transferId, 'createdAt', timestamp.toString()); @@ -1105,7 +1214,7 @@ describe('handleExecuted', () => { let tupleArray: Array = [ ethereum.Value.fromAddress(Address.fromString(DAO_ADDRESS)), ethereum.Value.fromAddress(Address.fromString(ADDRESS_THREE)), - ethereum.Value.fromUnsignedBigInt(BigInt.fromU32(10)) + ethereum.Value.fromUnsignedBigInt(BigInt.fromU32(10)), ]; let event = createExecutedEvent( @@ -1121,7 +1230,7 @@ describe('handleExecuted', () => { assert.entityCount('ERC20Contract', 1); assert.entityCount('ERC20Balance', 1); assert.entityCount('ERC20Transfer', 1); - eq('ERC20Balance', balanceId, 'balance', ERC20_AMOUNT_HALF); + eq('ERC20Balance', balanceEntityId, 'balance', ERC20_AMOUNT_HALF); // Mock balance of with different amount getBalanceOf(DAO_TOKEN_ADDRESS, DAO_ADDRESS, ERC20_AMOUNT_FULL); @@ -1134,7 +1243,7 @@ describe('handleExecuted', () => { assert.entityCount('ERC20Contract', 1); assert.entityCount('ERC20Balance', 1); assert.entityCount('ERC20Transfer', 2); - eq('ERC20Balance', balanceId, 'balance', ERC20_AMOUNT_FULL); + eq('ERC20Balance', balanceEntityId, 'balance', ERC20_AMOUNT_FULL); // Mock balance to get it back to the same before running this test getBalanceOf(DAO_TOKEN_ADDRESS, DAO_ADDRESS, ERC20_AMOUNT_HALF); @@ -1144,7 +1253,12 @@ describe('handleExecuted', () => { describe('ERC721 action', () => { beforeAll(() => { - createTokenCalls(DAO_TOKEN_ADDRESS, 'name', 'symbol', null, null); + createERC20TokenCalls( + DAO_TOKEN_ADDRESS, + ERC20_TOTAL_SUPPLY, + TOKEN_NAME, + TOKEN_SYMBOL + ); getSupportsInterface(DAO_TOKEN_ADDRESS, '0x01ffc9a7', true); getSupportsInterface(DAO_TOKEN_ADDRESS, '80ac58cd', true); @@ -1152,15 +1266,15 @@ describe('handleExecuted', () => { }); beforeEach(() => { - let entity = new ERC721Balance(balanceId); - entity.dao = daoId; + let entity = new ERC721Balance(balanceEntityId); + entity.dao = daoEntityId; entity.tokenIds = [ BigInt.fromI32(4), BigInt.fromI32(8), - BigInt.fromI32(12) + BigInt.fromI32(12), ]; entity.lastUpdated = BigInt.fromI32(2); - entity.token = tokenId; + entity.token = tokenEntityId; entity.save(); }); @@ -1171,7 +1285,7 @@ describe('handleExecuted', () => { let tupleArray: Array = [ ethereum.Value.fromAddress(Address.fromString(DAO_ADDRESS)), ethereum.Value.fromAddress(Address.fromString(ADDRESS_THREE)), - ethereum.Value.fromUnsignedBigInt(transferToKen) + ethereum.Value.fromUnsignedBigInt(transferToKen), ]; let event = createExecutedEvent( @@ -1186,40 +1300,52 @@ describe('handleExecuted', () => { let logIndex = event.transactionLogIndex; let timestamp = event.block.timestamp; - let transferId = getTransferId(txHash, logIndex, 0); + let transferId = generateTransferEntityId(txHash, logIndex, 0); handleExecuted(event); - let proposalId = event.params.actor - .toHexString() - .concat('_') - .concat(event.params.callId.toHexString()) - .concat('_') - .concat(event.transaction.hash.toHexString()) - .concat('_') - .concat(event.transactionLogIndex.toHexString()); + let proposalEntityId = generateProposalEntityId( + event.params.actor, + BigInt.fromUnsignedBytes(event.params.callId) + ); + let transactionActionsProposalEntityId = + generateTransactionActionsProposalEntityId( + proposalEntityId, + event.transaction.hash, + event.transactionLogIndex + ); // check ERC721Contract entity - eq('ERC721Contract', tokenId, 'id', tokenId); - eq('ERC721Contract', tokenId, 'name', 'name'); - eq('ERC721Contract', tokenId, 'symbol', 'symbol'); + eq('ERC721Contract', tokenEntityId, 'id', tokenEntityId); + eq('ERC721Contract', tokenEntityId, 'name', 'name'); + eq('ERC721Contract', tokenEntityId, 'symbol', 'symbol'); assert.entityCount('ERC721Contract', 1); // check ERC721Balance entity - eq('ERC721Balance', balanceId, 'id', balanceId); - eq('ERC721Balance', balanceId, 'token', tokenId); - eq('ERC721Balance', balanceId, 'dao', daoId); - eq('ERC721Balance', balanceId, 'tokenIds', '[4, 12]'); - eq('ERC721Balance', balanceId, 'lastUpdated', timestamp.toString()); + eq('ERC721Balance', balanceEntityId, 'id', balanceEntityId); + eq('ERC721Balance', balanceEntityId, 'token', tokenEntityId); + eq('ERC721Balance', balanceEntityId, 'dao', daoEntityId); + eq('ERC721Balance', balanceEntityId, 'tokenIds', '[4, 12]'); + eq( + 'ERC721Balance', + balanceEntityId, + 'lastUpdated', + timestamp.toString() + ); assert.entityCount('ERC721Balance', 1); // Check ERC721Transfer eq('ERC721Transfer', transferId, 'id', transferId); - eq('ERC721Transfer', transferId, 'dao', daoId); + eq('ERC721Transfer', transferId, 'dao', daoEntityId); eq('ERC721Transfer', transferId, 'tokenId', transferToKen.toString()); eq('ERC721Transfer', transferId, 'from', DAO_ADDRESS); eq('ERC721Transfer', transferId, 'to', ADDRESS_THREE); - eq('ERC721Transfer', transferId, 'proposal', proposalId); + eq( + 'ERC721Transfer', + transferId, + 'proposal', + transactionActionsProposalEntityId + ); eq('ERC721Transfer', transferId, 'type', 'Withdraw'); eq('ERC721Transfer', transferId, 'txHash', txHash.toHexString()); eq('ERC721Transfer', transferId, 'createdAt', timestamp.toString()); @@ -1252,7 +1378,7 @@ describe('handleExecuted', () => { assert.entityCount('ERC721Contract', 1); assert.entityCount('ERC721Balance', 1); assert.entityCount('ERC721Transfer', 2); - eq('ERC721Balance', balanceId, 'tokenIds', '[4]'); + eq('ERC721Balance', balanceEntityId, 'tokenIds', '[4]'); }); }); @@ -1264,7 +1390,7 @@ describe('handleExecuted', () => { ethereum.Value.fromAddress(Address.fromString(DAO_ADDRESS)), ethereum.Value.fromAddress(Address.fromString(ADDRESS_THREE)), ethereum.Value.fromUnsignedBigInt(transferToKen), - ethereum.Value.fromBytes(Bytes.fromHexString('0x')) + ethereum.Value.fromBytes(Bytes.fromHexString('0x')), ]; let event = createExecutedEvent( @@ -1279,40 +1405,52 @@ describe('handleExecuted', () => { let logIndex = event.transactionLogIndex; let timestamp = event.block.timestamp; - let transferId = getTransferId(txHash, logIndex, 0); + let transferId = generateTransferEntityId(txHash, logIndex, 0); handleExecuted(event); - let proposalId = event.params.actor - .toHexString() - .concat('_') - .concat(event.params.callId.toHexString()) - .concat('_') - .concat(event.transaction.hash.toHexString()) - .concat('_') - .concat(event.transactionLogIndex.toHexString()); + let proposalEntityId = generateProposalEntityId( + event.params.actor, + BigInt.fromUnsignedBytes(event.params.callId) + ); + let transactionActionsProposalEntityId = + generateTransactionActionsProposalEntityId( + proposalEntityId, + event.transaction.hash, + event.transactionLogIndex + ); // check ERC721Contract entity - eq('ERC721Contract', tokenId, 'id', tokenId); - eq('ERC721Contract', tokenId, 'name', 'name'); - eq('ERC721Contract', tokenId, 'symbol', 'symbol'); + eq('ERC721Contract', tokenEntityId, 'id', tokenEntityId); + eq('ERC721Contract', tokenEntityId, 'name', 'name'); + eq('ERC721Contract', tokenEntityId, 'symbol', 'symbol'); assert.entityCount('ERC721Contract', 1); // check ERC721Balance entity - eq('ERC721Balance', balanceId, 'id', balanceId); - eq('ERC721Balance', balanceId, 'token', tokenId); - eq('ERC721Balance', balanceId, 'dao', daoId); - eq('ERC721Balance', balanceId, 'tokenIds', '[4, 12]'); - eq('ERC721Balance', balanceId, 'lastUpdated', timestamp.toString()); + eq('ERC721Balance', balanceEntityId, 'id', balanceEntityId); + eq('ERC721Balance', balanceEntityId, 'token', tokenEntityId); + eq('ERC721Balance', balanceEntityId, 'dao', daoEntityId); + eq('ERC721Balance', balanceEntityId, 'tokenIds', '[4, 12]'); + eq( + 'ERC721Balance', + balanceEntityId, + 'lastUpdated', + timestamp.toString() + ); assert.entityCount('ERC721Balance', 1); // Check ERC721Transfer eq('ERC721Transfer', transferId, 'id', transferId); - eq('ERC721Transfer', transferId, 'dao', daoId); + eq('ERC721Transfer', transferId, 'dao', daoEntityId); eq('ERC721Transfer', transferId, 'tokenId', transferToKen.toString()); eq('ERC721Transfer', transferId, 'from', DAO_ADDRESS); eq('ERC721Transfer', transferId, 'to', ADDRESS_THREE); - eq('ERC721Transfer', transferId, 'proposal', proposalId); + eq( + 'ERC721Transfer', + transferId, + 'proposal', + transactionActionsProposalEntityId + ); eq('ERC721Transfer', transferId, 'type', 'Withdraw'); eq('ERC721Transfer', transferId, 'txHash', txHash.toHexString()); eq('ERC721Transfer', transferId, 'createdAt', timestamp.toString()); @@ -1347,7 +1485,7 @@ describe('handleExecuted', () => { assert.entityCount('ERC721Contract', 1); assert.entityCount('ERC721Balance', 1); assert.entityCount('ERC721Transfer', 2); - eq('ERC721Balance', balanceId, 'tokenIds', '[4]'); + eq('ERC721Balance', balanceEntityId, 'tokenIds', '[4]'); }); }); }); @@ -1376,32 +1514,6 @@ test('Run dao (handleTrustedForwarderSet) mappings with mock event', () => { clearStore(); }); -test('Run dao (handleSignatureValidatorSet) mappings with mock event', () => { - // create state - let entityID = Address.fromString(DAO_ADDRESS).toHexString(); - createDaoEntityState(entityID, ADDRESS_ONE, DAO_TOKEN_ADDRESS); - - let signatureValidator = ADDRESS_ONE; - - let newEvent = createSignatureValidatorSetEvent( - signatureValidator, - DAO_ADDRESS - ); - // handle event - handleSignatureValidatorSet(newEvent); - - // checks - assert.fieldEquals('Dao', entityID, 'id', entityID); - assert.fieldEquals( - 'Dao', - entityID, - 'signatureValidator', - Address.fromString(ADDRESS_ONE).toHexString() - ); - - clearStore(); -}); - test('Run dao (handleStandardCallbackRegistered) mappings with mock event', () => { // create state let daoAddress = Address.fromString(DAO_ADDRESS).toHexString(); diff --git a/packages/subgraph/tests/dao/dao_v1_3_0.test.ts b/packages/subgraph/tests/dao/dao_v1_3_0.test.ts index d2092ffbf..5979def77 100644 --- a/packages/subgraph/tests/dao/dao_v1_3_0.test.ts +++ b/packages/subgraph/tests/dao/dao_v1_3_0.test.ts @@ -1,32 +1,24 @@ -import {ethereum, Bytes, Address, BigInt} from '@graphprotocol/graph-ts'; -import { - describe, - test, - beforeEach, - afterEach, - clearStore, - assert, - beforeAll, - log -} from 'matchstick-as'; import { TransactionActionsProposal, Action, - ERC721Balance + ERC721Balance, } from '../../generated/schema'; import {Executed} from '../../generated/templates/DaoTemplateV1_3_0/DAO'; import {handleExecuted} from '../../src/dao/dao_v1_3_0'; +import {GOVERNANCE_WRAPPED_ERC20_INTERFACE_ID} from '../../src/utils/constants'; +import { + generateERC1155TransferEntityId, + generateTokenEntityId, +} from '../../src/utils/ids'; import { ERC20_transfer, - getTransferId, ERC20_transferFrom, ERC721_transferFrom, ERC721_safeTransferFromWithData, ERC1155_safeTransferFrom, ERC1155_safeBatchTransferFrom, ERC1155_INTERFACE_ID, - getTokenIdBalanceId, - getERC1155TransferId + ERC165_INTERFACE_ID, } from '../../src/utils/tokens/common'; import { DAO_ADDRESS, @@ -35,30 +27,53 @@ import { ADDRESS_THREE, ERC20_AMOUNT_FULL, CONTRACT_ADDRESS, - ZERO_BYTES32 + ZERO_BYTES32, + TOKEN_NAME, + TOKEN_SYMBOL, + ERC20_TOTAL_SUPPLY, + ERC20_DECIMALS, } from '../constants'; import { - createDummyActions, - createERC1155TokenCalls, - createTokenCalls -} from '../utils'; + ExtendedERC1155Balance, + ExtendedERC1155Contract, + ExtendedERC1155TokenIdBalance, + ExtendedERC1155Transfer, +} from '../helpers/extended-schema'; import { createNewExecutedEvent, encodeWithFunctionSelector, getBalanceOf, - getSupportsInterface + getSupportsInterface, } from './utils'; import { - ExtendedERC1155Balance, - ExtendedERC1155Contract, - ExtendedERC1155TokenIdBalance, - ExtendedERC1155Transfer -} from '../helpers/extended-schema'; + generateActionEntityId, + generateBalanceEntityId, + generateDaoEntityId, + generateProposalEntityId, + generateTokenIdBalanceEntityId, + generateTransactionActionsProposalEntityId, + generateTransferEntityId, + createDummyAction, + createERC20TokenCalls, + createERC1155TokenCalls, +} from '@aragon/osx-commons-subgraph'; +import {ethereum, Bytes, Address, BigInt} from '@graphprotocol/graph-ts'; +import { + describe, + test, + beforeEach, + afterEach, + clearStore, + assert, + beforeAll, +} from 'matchstick-as'; const eq = assert.fieldEquals; -let daoId = Address.fromString(DAO_ADDRESS).toHexString(); -let tokenId = Address.fromString(DAO_TOKEN_ADDRESS).toHexString(); -let balanceId = daoId.concat('_').concat(tokenId); +let daoAddress = Address.fromString(DAO_ADDRESS); +let tokenAddress = Address.fromString(DAO_TOKEN_ADDRESS); +let daoEntityId = generateDaoEntityId(daoAddress); +let tokenEntityId = generateTokenEntityId(tokenAddress); +let balanceEntityId = generateBalanceEntityId(daoAddress, tokenAddress); describe('handleExecuted', () => { afterEach(() => { @@ -71,7 +86,7 @@ describe('handleExecuted', () => { let execResults = [ Bytes.fromHexString('0x11'), - Bytes.fromHexString('0x22') + Bytes.fromHexString('0x22'), ]; let allowFailureMap = '2'; @@ -88,37 +103,57 @@ describe('handleExecuted', () => { handleExecuted(event); - let proposalId = event.params.actor - .toHexString() - .concat('_') - .concat(event.params.callId.toHexString()) - .concat('_') - .concat(event.transaction.hash.toHexString()) - .concat('_') - .concat(event.transactionLogIndex.toHexString()); + let proposalEntityId = generateProposalEntityId( + event.params.actor, + BigInt.fromUnsignedBytes(event.params.callId) + ); + let transactionActionsProposalEntityId = + generateTransactionActionsProposalEntityId( + proposalEntityId, + event.transaction.hash, + event.transactionLogIndex + ); assert.entityCount('TransactionActionsProposal', 1); assert.entityCount('Action', 2); - eq('TransactionActionsProposal', proposalId, 'id', proposalId); - eq('TransactionActionsProposal', proposalId, 'failureMap', failureMap); eq( 'TransactionActionsProposal', - proposalId, + transactionActionsProposalEntityId, + 'id', + transactionActionsProposalEntityId + ); + eq( + 'TransactionActionsProposal', + transactionActionsProposalEntityId, + 'failureMap', + failureMap + ); + eq( + 'TransactionActionsProposal', + transactionActionsProposalEntityId, 'allowFailureMap', allowFailureMap ); for (let i = 0; i < event.params.actions.length; i++) { - let actionId = proposalId.concat('_').concat(i.toString()); + let actionEntityId = generateActionEntityId( + transactionActionsProposalEntityId, + i + ); - eq('Action', actionId, 'id', actionId); - eq('Action', actionId, 'execResult', execResults[i].toHexString()); - eq('Action', actionId, 'dao', DAO_ADDRESS); - eq('Action', actionId, 'proposal', proposalId); + eq('Action', actionEntityId, 'id', actionEntityId); + eq('Action', actionEntityId, 'execResult', execResults[i].toHexString()); + eq('Action', actionEntityId, 'dao', DAO_ADDRESS); eq( 'Action', - actionId, + actionEntityId, + 'proposal', + transactionActionsProposalEntityId + ); + eq( + 'Action', + actionEntityId, 'data', encodeWithFunctionSelector(tuple, selector).toHexString() ); @@ -141,19 +176,26 @@ describe('handleExecuted', () => { failureMap ); - let proposalId = event.params.actor - .toHexString() - .concat('_') - .concat(event.params.callId.toHexString()) - .concat('_') - .concat(event.transaction.hash.toHexString()) - .concat('_') - .concat(event.transactionLogIndex.toHexString()); + let proposalEntityId = generateProposalEntityId( + event.params.actor, + BigInt.fromUnsignedBytes(event.params.callId) + ); + let transactionActionsProposalEntityId = + generateTransactionActionsProposalEntityId( + proposalEntityId, + event.transaction.hash, + event.transactionLogIndex + ); - let actionId = proposalId.concat('_').concat('0'); + let actionEntityId = generateActionEntityId( + transactionActionsProposalEntityId, + 0 + ); // create proposal - let proposal = new TransactionActionsProposal(proposalId); + let proposal = new TransactionActionsProposal( + transactionActionsProposalEntityId + ); proposal.dao = event.address.toHexString(); proposal.createdAt = event.block.timestamp; proposal.endDate = event.block.timestamp; @@ -165,7 +207,7 @@ describe('handleExecuted', () => { proposal.save(); // create action - let action = new Action(actionId); + let action = new Action(actionEntityId); action.to = Address.fromString(DAO_TOKEN_ADDRESS); action.data = Bytes.fromHexString('0x'); action.value = BigInt.zero(); @@ -185,14 +227,24 @@ describe('handleExecuted', () => { assert.entityCount('Action', 1); assert.entityCount('TransactionActionsProposal', 1); - eq('Action', actionId, 'id', actionId); - eq('Action', actionId, 'execResult', execResult.toHexString()); + eq('Action', actionEntityId, 'id', actionEntityId); + eq('Action', actionEntityId, 'execResult', execResult.toHexString()); - eq('TransactionActionsProposal', proposalId, 'id', proposalId); - eq('TransactionActionsProposal', proposalId, 'failureMap', failureMap); eq( 'TransactionActionsProposal', - proposalId, + transactionActionsProposalEntityId, + 'id', + transactionActionsProposalEntityId + ); + eq( + 'TransactionActionsProposal', + transactionActionsProposalEntityId, + 'failureMap', + failureMap + ); + eq( + 'TransactionActionsProposal', + transactionActionsProposalEntityId, 'allowFailureMap', allowFailureMap ); @@ -200,7 +252,13 @@ describe('handleExecuted', () => { describe('ERC20 action', () => { beforeAll(() => { - createTokenCalls(DAO_TOKEN_ADDRESS, 'name', 'symbol', '6', '10'); + createERC20TokenCalls( + DAO_TOKEN_ADDRESS, + ERC20_TOTAL_SUPPLY, + TOKEN_NAME, + TOKEN_SYMBOL, + ERC20_DECIMALS + ); getBalanceOf(DAO_TOKEN_ADDRESS, DAO_ADDRESS, ERC20_AMOUNT_HALF); getBalanceOf(DAO_TOKEN_ADDRESS, DAO_TOKEN_ADDRESS, ERC20_AMOUNT_HALF); @@ -216,11 +274,28 @@ describe('handleExecuted', () => { }); describe('ERC20 transfer action', () => { + beforeAll(() => { + createERC20TokenCalls( + DAO_TOKEN_ADDRESS, + ERC20_TOTAL_SUPPLY, + TOKEN_NAME, + TOKEN_SYMBOL + ); + + getSupportsInterface(DAO_TOKEN_ADDRESS, ERC165_INTERFACE_ID, true); + getSupportsInterface( + DAO_TOKEN_ADDRESS, + GOVERNANCE_WRAPPED_ERC20_INTERFACE_ID, + false + ); + getSupportsInterface(DAO_TOKEN_ADDRESS, 'ffffffff', false); + }); + test('creates entities with correct values', () => { let transferToken = BigInt.fromU32(10); let tupleArray: Array = [ ethereum.Value.fromAddress(Address.fromString(ADDRESS_THREE)), - ethereum.Value.fromUnsignedBigInt(transferToken) + ethereum.Value.fromUnsignedBigInt(transferToken), ]; let event = createExecutedEvent( @@ -234,42 +309,54 @@ describe('handleExecuted', () => { handleExecuted(event); - let proposalId = event.params.actor - .toHexString() - .concat('_') - .concat(event.params.callId.toHexString()) - .concat('_') - .concat(event.transaction.hash.toHexString()) - .concat('_') - .concat(event.transactionLogIndex.toHexString()); + let proposalEntityId = generateProposalEntityId( + event.params.actor, + BigInt.fromUnsignedBytes(event.params.callId) + ); + let transactionActionsProposalEntityId = + generateTransactionActionsProposalEntityId( + proposalEntityId, + event.transaction.hash, + event.transactionLogIndex + ); let txHash = event.transaction.hash; let logIndex = event.transactionLogIndex; let timestamp = event.block.timestamp; - let transferId = getTransferId(txHash, logIndex, 0); + let transferId = generateTransferEntityId(txHash, logIndex, 0); // check ERC20Contract entity - eq('ERC20Contract', tokenId, 'id', tokenId); - eq('ERC20Contract', tokenId, 'name', 'name'); - eq('ERC20Contract', tokenId, 'symbol', 'symbol'); + eq('ERC20Contract', tokenEntityId, 'id', tokenEntityId); + eq('ERC20Contract', tokenEntityId, 'name', TOKEN_NAME); + eq('ERC20Contract', tokenEntityId, 'symbol', TOKEN_SYMBOL); assert.entityCount('ERC20Contract', 1); // check ERC20Balance entity - eq('ERC20Balance', balanceId, 'id', balanceId); - eq('ERC20Balance', balanceId, 'token', tokenId); - eq('ERC20Balance', balanceId, 'dao', daoId); - eq('ERC20Balance', balanceId, 'balance', ERC20_AMOUNT_HALF); - eq('ERC20Balance', balanceId, 'lastUpdated', timestamp.toString()); + eq('ERC20Balance', balanceEntityId, 'id', balanceEntityId); + eq('ERC20Balance', balanceEntityId, 'token', tokenEntityId); + eq('ERC20Balance', balanceEntityId, 'dao', daoEntityId); + eq('ERC20Balance', balanceEntityId, 'balance', ERC20_AMOUNT_HALF); + eq( + 'ERC20Balance', + balanceEntityId, + 'lastUpdated', + timestamp.toString() + ); assert.entityCount('ERC20Balance', 1); // Check ERC20Transfer eq('ERC20Transfer', transferId, 'id', transferId); - eq('ERC20Transfer', transferId, 'dao', daoId); + eq('ERC20Transfer', transferId, 'dao', daoEntityId); eq('ERC20Transfer', transferId, 'amount', transferToken.toString()); eq('ERC20Transfer', transferId, 'from', DAO_ADDRESS); eq('ERC20Transfer', transferId, 'to', ADDRESS_THREE); - eq('ERC20Transfer', transferId, 'proposal', proposalId); + eq( + 'ERC20Transfer', + transferId, + 'proposal', + transactionActionsProposalEntityId + ); eq('ERC20Transfer', transferId, 'type', 'Withdraw'); eq('ERC20Transfer', transferId, 'txHash', txHash.toHexString()); eq('ERC20Transfer', transferId, 'createdAt', timestamp.toString()); @@ -279,7 +366,7 @@ describe('handleExecuted', () => { test('correctly handles multiple events and updates balance', () => { let tupleArray: Array = [ ethereum.Value.fromAddress(Address.fromString(ADDRESS_THREE)), - ethereum.Value.fromUnsignedBigInt(BigInt.fromU32(10)) + ethereum.Value.fromUnsignedBigInt(BigInt.fromU32(10)), ]; let event = createExecutedEvent( @@ -297,7 +384,7 @@ describe('handleExecuted', () => { assert.entityCount('ERC20Contract', 1); assert.entityCount('ERC20Transfer', 1); assert.entityCount('ERC20Balance', 1); - eq('ERC20Balance', balanceId, 'balance', ERC20_AMOUNT_HALF); + eq('ERC20Balance', balanceEntityId, 'balance', ERC20_AMOUNT_HALF); // Mock balance of with different amount getBalanceOf(DAO_TOKEN_ADDRESS, DAO_ADDRESS, ERC20_AMOUNT_FULL); @@ -311,7 +398,7 @@ describe('handleExecuted', () => { assert.entityCount('ERC20Contract', 1); assert.entityCount('ERC20Transfer', 2); assert.entityCount('ERC20Balance', 1); - eq('ERC20Balance', balanceId, 'balance', ERC20_AMOUNT_FULL); + eq('ERC20Balance', balanceEntityId, 'balance', ERC20_AMOUNT_FULL); // Mock balance to get it back to the same before running this test getBalanceOf(DAO_TOKEN_ADDRESS, DAO_ADDRESS, ERC20_AMOUNT_HALF); @@ -324,7 +411,7 @@ describe('handleExecuted', () => { let tupleArray: Array = [ ethereum.Value.fromAddress(Address.fromString(DAO_ADDRESS)), ethereum.Value.fromAddress(Address.fromString(ADDRESS_THREE)), - ethereum.Value.fromUnsignedBigInt(transferToken) + ethereum.Value.fromUnsignedBigInt(transferToken), ]; let event = createExecutedEvent( @@ -338,42 +425,54 @@ describe('handleExecuted', () => { handleExecuted(event); - let proposalId = event.params.actor - .toHexString() - .concat('_') - .concat(event.params.callId.toHexString()) - .concat('_') - .concat(event.transaction.hash.toHexString()) - .concat('_') - .concat(event.transactionLogIndex.toHexString()); + let proposalEntityId = generateProposalEntityId( + event.params.actor, + BigInt.fromUnsignedBytes(event.params.callId) + ); + let transactionActionsProposalEntityId = + generateTransactionActionsProposalEntityId( + proposalEntityId, + event.transaction.hash, + event.transactionLogIndex + ); let txHash = event.transaction.hash; let logIndex = event.transactionLogIndex; let timestamp = event.block.timestamp; - let transferId = getTransferId(txHash, logIndex, 0); + let transferId = generateTransferEntityId(txHash, logIndex, 0); // check ERC20Contract entity - eq('ERC20Contract', tokenId, 'id', tokenId); - eq('ERC20Contract', tokenId, 'name', 'name'); - eq('ERC20Contract', tokenId, 'symbol', 'symbol'); + eq('ERC20Contract', tokenEntityId, 'id', tokenEntityId); + eq('ERC20Contract', tokenEntityId, 'name', TOKEN_NAME); + eq('ERC20Contract', tokenEntityId, 'symbol', TOKEN_SYMBOL); assert.entityCount('ERC20Contract', 1); // check ERC20Balance entity - eq('ERC20Balance', balanceId, 'id', balanceId); - eq('ERC20Balance', balanceId, 'token', tokenId); - eq('ERC20Balance', balanceId, 'dao', daoId); - eq('ERC20Balance', balanceId, 'balance', ERC20_AMOUNT_HALF); - eq('ERC20Balance', balanceId, 'lastUpdated', timestamp.toString()); + eq('ERC20Balance', balanceEntityId, 'id', balanceEntityId); + eq('ERC20Balance', balanceEntityId, 'token', tokenEntityId); + eq('ERC20Balance', balanceEntityId, 'dao', daoEntityId); + eq('ERC20Balance', balanceEntityId, 'balance', ERC20_AMOUNT_HALF); + eq( + 'ERC20Balance', + balanceEntityId, + 'lastUpdated', + timestamp.toString() + ); assert.entityCount('ERC20Balance', 1); // Check ERC20Transfer eq('ERC20Transfer', transferId, 'id', transferId); - eq('ERC20Transfer', transferId, 'dao', daoId); + eq('ERC20Transfer', transferId, 'dao', daoEntityId); eq('ERC20Transfer', transferId, 'amount', transferToken.toString()); eq('ERC20Transfer', transferId, 'from', DAO_ADDRESS); eq('ERC20Transfer', transferId, 'to', ADDRESS_THREE); - eq('ERC20Transfer', transferId, 'proposal', proposalId); + eq( + 'ERC20Transfer', + transferId, + 'proposal', + transactionActionsProposalEntityId + ); eq('ERC20Transfer', transferId, 'type', 'Withdraw'); eq('ERC20Transfer', transferId, 'txHash', txHash.toHexString()); eq('ERC20Transfer', transferId, 'createdAt', timestamp.toString()); @@ -384,7 +483,7 @@ describe('handleExecuted', () => { let tupleArray: Array = [ ethereum.Value.fromAddress(Address.fromString(DAO_ADDRESS)), ethereum.Value.fromAddress(Address.fromString(ADDRESS_THREE)), - ethereum.Value.fromUnsignedBigInt(BigInt.fromU32(10)) + ethereum.Value.fromUnsignedBigInt(BigInt.fromU32(10)), ]; let event = createExecutedEvent( @@ -401,7 +500,7 @@ describe('handleExecuted', () => { assert.entityCount('ERC20Contract', 1); assert.entityCount('ERC20Balance', 1); assert.entityCount('ERC20Transfer', 1); - eq('ERC20Balance', balanceId, 'balance', ERC20_AMOUNT_HALF); + eq('ERC20Balance', balanceEntityId, 'balance', ERC20_AMOUNT_HALF); // Mock balance of with different amount getBalanceOf(DAO_TOKEN_ADDRESS, DAO_ADDRESS, ERC20_AMOUNT_FULL); @@ -414,7 +513,7 @@ describe('handleExecuted', () => { assert.entityCount('ERC20Contract', 1); assert.entityCount('ERC20Balance', 1); assert.entityCount('ERC20Transfer', 2); - eq('ERC20Balance', balanceId, 'balance', ERC20_AMOUNT_FULL); + eq('ERC20Balance', balanceEntityId, 'balance', ERC20_AMOUNT_FULL); // Mock balance to get it back to the same before running this test getBalanceOf(DAO_TOKEN_ADDRESS, DAO_ADDRESS, ERC20_AMOUNT_HALF); @@ -424,7 +523,12 @@ describe('handleExecuted', () => { describe('ERC721 action', () => { beforeAll(() => { - createTokenCalls(DAO_TOKEN_ADDRESS, 'name', 'symbol', null, null); + createERC20TokenCalls( + DAO_TOKEN_ADDRESS, + ERC20_TOTAL_SUPPLY, + TOKEN_NAME, + TOKEN_SYMBOL + ); getSupportsInterface(DAO_TOKEN_ADDRESS, '0x01ffc9a7', true); getSupportsInterface(DAO_TOKEN_ADDRESS, '80ac58cd', true); @@ -432,15 +536,15 @@ describe('handleExecuted', () => { }); beforeEach(() => { - let entity = new ERC721Balance(balanceId); - entity.dao = daoId; + let entity = new ERC721Balance(balanceEntityId); + entity.dao = daoEntityId; entity.tokenIds = [ BigInt.fromI32(4), BigInt.fromI32(8), - BigInt.fromI32(12) + BigInt.fromI32(12), ]; entity.lastUpdated = BigInt.fromI32(2); - entity.token = tokenId; + entity.token = tokenEntityId; entity.save(); }); @@ -451,7 +555,7 @@ describe('handleExecuted', () => { let tupleArray: Array = [ ethereum.Value.fromAddress(Address.fromString(DAO_ADDRESS)), ethereum.Value.fromAddress(Address.fromString(ADDRESS_THREE)), - ethereum.Value.fromUnsignedBigInt(transferToKen) + ethereum.Value.fromUnsignedBigInt(transferToKen), ]; let event = createExecutedEvent( @@ -467,40 +571,52 @@ describe('handleExecuted', () => { let logIndex = event.transactionLogIndex; let timestamp = event.block.timestamp; - let transferId = getTransferId(txHash, logIndex, 0); + let transferId = generateTransferEntityId(txHash, logIndex, 0); handleExecuted(event); - let proposalId = event.params.actor - .toHexString() - .concat('_') - .concat(event.params.callId.toHexString()) - .concat('_') - .concat(event.transaction.hash.toHexString()) - .concat('_') - .concat(event.transactionLogIndex.toHexString()); + let proposalEntityId = generateProposalEntityId( + event.params.actor, + BigInt.fromUnsignedBytes(event.params.callId) + ); + let transactionActionsProposalEntityId = + generateTransactionActionsProposalEntityId( + proposalEntityId, + event.transaction.hash, + event.transactionLogIndex + ); // check ERC721Contract entity - eq('ERC721Contract', tokenId, 'id', tokenId); - eq('ERC721Contract', tokenId, 'name', 'name'); - eq('ERC721Contract', tokenId, 'symbol', 'symbol'); + eq('ERC721Contract', tokenEntityId, 'id', tokenEntityId); + eq('ERC721Contract', tokenEntityId, 'name', TOKEN_NAME); + eq('ERC721Contract', tokenEntityId, 'symbol', TOKEN_SYMBOL); assert.entityCount('ERC721Contract', 1); // check ERC721Balance entity - eq('ERC721Balance', balanceId, 'id', balanceId); - eq('ERC721Balance', balanceId, 'token', tokenId); - eq('ERC721Balance', balanceId, 'dao', daoId); - eq('ERC721Balance', balanceId, 'tokenIds', '[4, 12]'); - eq('ERC721Balance', balanceId, 'lastUpdated', timestamp.toString()); + eq('ERC721Balance', balanceEntityId, 'id', balanceEntityId); + eq('ERC721Balance', balanceEntityId, 'token', tokenEntityId); + eq('ERC721Balance', balanceEntityId, 'dao', daoEntityId); + eq('ERC721Balance', balanceEntityId, 'tokenIds', '[4, 12]'); + eq( + 'ERC721Balance', + balanceEntityId, + 'lastUpdated', + timestamp.toString() + ); assert.entityCount('ERC721Balance', 1); // Check ERC721Transfer eq('ERC721Transfer', transferId, 'id', transferId); - eq('ERC721Transfer', transferId, 'dao', daoId); + eq('ERC721Transfer', transferId, 'dao', daoEntityId); eq('ERC721Transfer', transferId, 'tokenId', transferToKen.toString()); eq('ERC721Transfer', transferId, 'from', DAO_ADDRESS); eq('ERC721Transfer', transferId, 'to', ADDRESS_THREE); - eq('ERC721Transfer', transferId, 'proposal', proposalId); + eq( + 'ERC721Transfer', + transferId, + 'proposal', + transactionActionsProposalEntityId + ); eq('ERC721Transfer', transferId, 'type', 'Withdraw'); eq('ERC721Transfer', transferId, 'txHash', txHash.toHexString()); eq('ERC721Transfer', transferId, 'createdAt', timestamp.toString()); @@ -534,7 +650,7 @@ describe('handleExecuted', () => { assert.entityCount('ERC721Contract', 1); assert.entityCount('ERC721Balance', 1); assert.entityCount('ERC721Transfer', 2); - eq('ERC721Balance', balanceId, 'tokenIds', '[4]'); + eq('ERC721Balance', balanceEntityId, 'tokenIds', '[4]'); }); }); @@ -546,7 +662,7 @@ describe('handleExecuted', () => { ethereum.Value.fromAddress(Address.fromString(DAO_ADDRESS)), ethereum.Value.fromAddress(Address.fromString(ADDRESS_THREE)), ethereum.Value.fromUnsignedBigInt(transferToKen), - ethereum.Value.fromBytes(Bytes.fromHexString('0x')) + ethereum.Value.fromBytes(Bytes.fromHexString('0x')), ]; let event = createExecutedEvent( @@ -562,40 +678,52 @@ describe('handleExecuted', () => { let logIndex = event.transactionLogIndex; let timestamp = event.block.timestamp; - let transferId = getTransferId(txHash, logIndex, 0); + let transferId = generateTransferEntityId(txHash, logIndex, 0); handleExecuted(event); - let proposalId = event.params.actor - .toHexString() - .concat('_') - .concat(event.params.callId.toHexString()) - .concat('_') - .concat(event.transaction.hash.toHexString()) - .concat('_') - .concat(event.transactionLogIndex.toHexString()); + let proposalEntityId = generateProposalEntityId( + event.params.actor, + BigInt.fromUnsignedBytes(event.params.callId) + ); + let transactionActionsProposalEntityId = + generateTransactionActionsProposalEntityId( + proposalEntityId, + event.transaction.hash, + event.transactionLogIndex + ); // check ERC721Contract entity - eq('ERC721Contract', tokenId, 'id', tokenId); - eq('ERC721Contract', tokenId, 'name', 'name'); - eq('ERC721Contract', tokenId, 'symbol', 'symbol'); + eq('ERC721Contract', tokenEntityId, 'id', tokenEntityId); + eq('ERC721Contract', tokenEntityId, 'name', TOKEN_NAME); + eq('ERC721Contract', tokenEntityId, 'symbol', TOKEN_SYMBOL); assert.entityCount('ERC721Contract', 1); // check ERC721Balance entity - eq('ERC721Balance', balanceId, 'id', balanceId); - eq('ERC721Balance', balanceId, 'token', tokenId); - eq('ERC721Balance', balanceId, 'dao', daoId); - eq('ERC721Balance', balanceId, 'tokenIds', '[4, 12]'); - eq('ERC721Balance', balanceId, 'lastUpdated', timestamp.toString()); + eq('ERC721Balance', balanceEntityId, 'id', balanceEntityId); + eq('ERC721Balance', balanceEntityId, 'token', tokenEntityId); + eq('ERC721Balance', balanceEntityId, 'dao', daoEntityId); + eq('ERC721Balance', balanceEntityId, 'tokenIds', '[4, 12]'); + eq( + 'ERC721Balance', + balanceEntityId, + 'lastUpdated', + timestamp.toString() + ); assert.entityCount('ERC721Balance', 1); // Check ERC721Transfer eq('ERC721Transfer', transferId, 'id', transferId); - eq('ERC721Transfer', transferId, 'dao', daoId); + eq('ERC721Transfer', transferId, 'dao', daoEntityId); eq('ERC721Transfer', transferId, 'tokenId', transferToKen.toString()); eq('ERC721Transfer', transferId, 'from', DAO_ADDRESS); eq('ERC721Transfer', transferId, 'to', ADDRESS_THREE); - eq('ERC721Transfer', transferId, 'proposal', proposalId); + eq( + 'ERC721Transfer', + transferId, + 'proposal', + transactionActionsProposalEntityId + ); eq('ERC721Transfer', transferId, 'type', 'Withdraw'); eq('ERC721Transfer', transferId, 'txHash', txHash.toHexString()); eq('ERC721Transfer', transferId, 'createdAt', timestamp.toString()); @@ -631,7 +759,7 @@ describe('handleExecuted', () => { assert.entityCount('ERC721Contract', 1); assert.entityCount('ERC721Balance', 1); assert.entityCount('ERC721Transfer', 2); - eq('ERC721Balance', balanceId, 'tokenIds', '[4]'); + eq('ERC721Balance', balanceEntityId, 'tokenIds', '[4]'); }); }); }); @@ -669,7 +797,7 @@ describe('handleExecuted', () => { ethereum.Value.fromAddress(Address.fromString(ADDRESS_THREE)), // to ethereum.Value.fromUnsignedBigInt(transferToken), // tokenId ethereum.Value.fromUnsignedBigInt(amount), // amount - ethereum.Value.fromBytes(Bytes.fromHexString('0x')) // data + ethereum.Value.fromBytes(Bytes.fromHexString('0x')), // data ]; let event = createExecutedEvent( [tupleArray], @@ -682,11 +810,7 @@ describe('handleExecuted', () => { handleExecuted(event); let timestamp = event.block.timestamp; - let tokenIdBalanceId = getTokenIdBalanceId( - daoId, - tokenId, - transferToken - ); + // check ERC1155Contract entity assert.entityCount('ERC1155Contract', 1); let erc1155Contract = new ExtendedERC1155Contract().withDefaultValues(); @@ -700,7 +824,8 @@ describe('handleExecuted', () => { // check ERC1155TokenIdBalance entity assert.entityCount('ERC1155TokenIdBalance', 1); - let erc1155TokenIdBalance = new ExtendedERC1155TokenIdBalance().withDefaultValues(); + let erc1155TokenIdBalance = + new ExtendedERC1155TokenIdBalance().withDefaultValues(); erc1155TokenIdBalance.amount = amount; erc1155TokenIdBalance.lastUpdated = timestamp; erc1155TokenIdBalance.assertEntity(); @@ -708,22 +833,29 @@ describe('handleExecuted', () => { // check ERC1155Transfer entity let txHash = event.transaction.hash; let logIndex = event.transactionLogIndex; - let transferId = getERC1155TransferId(txHash, logIndex, 0, 0); - let proposalId = event.params.actor - .toHexString() - .concat('_') - .concat(event.params.callId.toHexString()) - .concat('_') - .concat(txHash.toHexString()) - .concat('_') - .concat(logIndex.toHexString()); + let transferId = generateERC1155TransferEntityId( + txHash, + logIndex, + 0, + 0 + ); + let proposalEntityId = generateProposalEntityId( + event.params.actor, + BigInt.fromUnsignedBytes(event.params.callId) + ); + let transactionActionsProposalEntityId = + generateTransactionActionsProposalEntityId( + proposalEntityId, + event.transaction.hash, + event.transactionLogIndex + ); assert.entityCount('ERC1155Transfer', 1); let erc1155Transfer = new ExtendedERC1155Transfer().withDefaultValues(); erc1155Transfer.id = transferId; erc1155Transfer.amount = amount; - erc1155Transfer.from = Address.fromHexString(daoId); + erc1155Transfer.from = Address.fromHexString(daoEntityId); erc1155Transfer.to = Address.fromHexString(ADDRESS_THREE); - erc1155Transfer.proposal = proposalId; + erc1155Transfer.proposal = transactionActionsProposalEntityId; erc1155Transfer.type = 'Withdraw'; erc1155Transfer.txHash = txHash; erc1155Transfer.createdAt = timestamp; @@ -739,7 +871,7 @@ describe('handleExecuted', () => { ethereum.Value.fromAddress(Address.fromString(ADDRESS_THREE)), ethereum.Value.fromUnsignedBigInt(transferTokens[i]), ethereum.Value.fromUnsignedBigInt(amount), - ethereum.Value.fromBytes(Bytes.fromHexString('0x')) + ethereum.Value.fromBytes(Bytes.fromHexString('0x')), ]); } @@ -756,7 +888,11 @@ describe('handleExecuted', () => { let erc1155TokenIdBalanceIdArray: string[] = []; for (let i = 0; i < transferTokens.length; i++) { erc1155TokenIdBalanceIdArray.push( - balanceId.concat('_').concat(transferTokens[i].toString()) + generateTokenIdBalanceEntityId( + daoAddress, + tokenAddress, + transferTokens[i] + ) ); } @@ -771,19 +907,19 @@ describe('handleExecuted', () => { let tokenIds = [ BigInt.fromI32(0), BigInt.fromI32(1), - BigInt.fromI32(2) + BigInt.fromI32(2), ]; let amounts = [ BigInt.fromI32(10), BigInt.fromI32(10), - BigInt.fromI32(10) + BigInt.fromI32(10), ]; let tupleArray: Array = [ ethereum.Value.fromAddress(Address.fromString(DAO_ADDRESS)), // from ethereum.Value.fromAddress(Address.fromString(ADDRESS_THREE)), // to ethereum.Value.fromUnsignedBigIntArray(tokenIds), // tokenIds ethereum.Value.fromUnsignedBigIntArray(amounts), // amounts - ethereum.Value.fromBytes(Bytes.fromHexString('0x')) // data + ethereum.Value.fromBytes(Bytes.fromHexString('0x')), // data ]; let event = createExecutedEvent( [tupleArray], @@ -800,7 +936,11 @@ describe('handleExecuted', () => { // iterate over tokenIds for (let i = 0; i < tokenIds.length; i++) { tokenIdBalanceIdArray.push( - balanceId.concat('_').concat(tokenIds[i].toString()) + generateTokenIdBalanceEntityId( + daoAddress, + tokenAddress, + tokenIds[i] + ) ); } // check ERC1155Contract entity @@ -815,34 +955,43 @@ describe('handleExecuted', () => { // check ERC1155TokenIdBalance entity assert.entityCount('ERC1155TokenIdBalance', 3); for (let i = 0; i < tokenIds.length; i++) { - let erc1155TokenIdBalance = new ExtendedERC1155TokenIdBalance().withDefaultValues(); + let erc1155TokenIdBalance = + new ExtendedERC1155TokenIdBalance().withDefaultValues(); erc1155TokenIdBalance.id = tokenIdBalanceIdArray[i]; erc1155TokenIdBalance.tokenId = tokenIds[i]; erc1155TokenIdBalance.amount = amounts[i]; erc1155TokenIdBalance.lastUpdated = timestamp; - erc1155TokenIdBalance.balance = balanceId; + erc1155TokenIdBalance.balance = balanceEntityId; erc1155TokenIdBalance.assertEntity(); } // check ERC1155Transfer entity let txHash = event.transaction.hash; let logIndex = event.transactionLogIndex; - let proposalId = event.params.actor - .toHexString() - .concat('_') - .concat(event.params.callId.toHexString()) - .concat('_') - .concat(txHash.toHexString()) - .concat('_') - .concat(logIndex.toHexString()); + let proposalEntityId = generateProposalEntityId( + event.params.actor, + BigInt.fromUnsignedBytes(event.params.callId) + ); + let transactionActionsProposalEntityId = + generateTransactionActionsProposalEntityId( + proposalEntityId, + event.transaction.hash, + event.transactionLogIndex + ); for (let i = 0; i < tokenIds.length; i++) { - let erc1155Transfer = new ExtendedERC1155Transfer().withDefaultValues(); - erc1155Transfer.id = getERC1155TransferId(txHash, logIndex, 0, i); + let erc1155Transfer = + new ExtendedERC1155Transfer().withDefaultValues(); + erc1155Transfer.id = generateERC1155TransferEntityId( + txHash, + logIndex, + 0, + i + ); // appeend index to transferId to make sure it is unique erc1155Transfer.amount = amounts[i]; - erc1155Transfer.from = Address.fromHexString(daoId); + erc1155Transfer.from = Address.fromHexString(daoEntityId); erc1155Transfer.to = Address.fromHexString(ADDRESS_THREE); erc1155Transfer.tokenId = tokenIds[i]; - erc1155Transfer.proposal = proposalId; + erc1155Transfer.proposal = transactionActionsProposalEntityId; erc1155Transfer.type = 'Withdraw'; erc1155Transfer.txHash = txHash; erc1155Transfer.createdAt = timestamp; @@ -852,11 +1001,11 @@ describe('handleExecuted', () => { test('correctly handles multiple events and updates balance', () => { let tokenIds = [ [BigInt.fromI32(0), BigInt.fromI32(1), BigInt.fromI32(2)], - [BigInt.fromI32(0), BigInt.fromI32(1), BigInt.fromI32(2)] + [BigInt.fromI32(0), BigInt.fromI32(1), BigInt.fromI32(2)], ]; let amounts = [ [BigInt.fromI32(10), BigInt.fromI32(10), BigInt.fromI32(10)], - [BigInt.fromI32(30), BigInt.fromI32(30), BigInt.fromI32(30)] + [BigInt.fromI32(30), BigInt.fromI32(30), BigInt.fromI32(30)], ]; let tuples: ethereum.Value[][] = []; for (let i = 0; i < tokenIds.length; i++) { @@ -865,7 +1014,7 @@ describe('handleExecuted', () => { ethereum.Value.fromAddress(Address.fromString(ADDRESS_THREE)), // to ethereum.Value.fromUnsignedBigIntArray(tokenIds[i]), // tokenIds ethereum.Value.fromUnsignedBigIntArray(amounts[i]), // amounts - ethereum.Value.fromBytes(Bytes.fromHexString('0x')) // data + ethereum.Value.fromBytes(Bytes.fromHexString('0x')), // data ]); } let event = createExecutedEvent( @@ -880,7 +1029,11 @@ describe('handleExecuted', () => { let erc1155TokenIdBalanceIdArray: string[] = []; for (let i = 0; i < tokenIds[0].length; i++) { erc1155TokenIdBalanceIdArray.push( - getTokenIdBalanceId(daoId, tokenId, tokenIds[0][i]) + generateTokenIdBalanceEntityId( + daoAddress, + tokenAddress, + tokenIds[0][i] + ) ); } // check ERC ontract entity @@ -913,13 +1066,13 @@ function createExecutedEvent( isDynamic ); - let action = createDummyActions( + let action = createDummyAction( DAO_TOKEN_ADDRESS, '0', functionData.toHexString() ); - actions.push(action[0]); + actions.push(action); } if (execResults.length == 0) { diff --git a/packages/subgraph/tests/dao/permission-manager.test.ts b/packages/subgraph/tests/dao/permission-manager.test.ts deleted file mode 100644 index d1a3a42e0..000000000 --- a/packages/subgraph/tests/dao/permission-manager.test.ts +++ /dev/null @@ -1,209 +0,0 @@ -import {assert, clearStore, test} from 'matchstick-as/assembly/index'; -import {Address, ByteArray, Bytes, crypto} from '@graphprotocol/graph-ts'; - -import {handleGranted, handleRevoked} from '../../src/dao/dao_v1_0_0'; -import {Permission, ContractPermissionId} from '../../generated/schema'; -import { - DAO_ADDRESS, - ADDRESS_ONE, - CONTRACT_ADDRESS, - ADDRESS_TWO -} from '../constants'; -import { - createNewGrantedEvent, - createNewRevokedEvent, - getEXECUTE_PERMISSION_ID, - getEXECUTE_PERMISSION_IDreverted -} from './utils'; - -let contractPermissionId = Bytes.fromByteArray( - crypto.keccak256(ByteArray.fromUTF8('EXECUTE_PERMISSION')) -); - -test('Run dao (handleGranted) mappings with mock event', () => { - // create event and run it's handler - let grantedEvent = createNewGrantedEvent( - contractPermissionId, - ADDRESS_ONE, - DAO_ADDRESS, - CONTRACT_ADDRESS, - ADDRESS_TWO, - DAO_ADDRESS - ); - - // handle event - handleGranted(grantedEvent); - - // checks - // contractPermissionId - let contractPermissionIdEntityID = - Address.fromString(DAO_ADDRESS).toHexString() + - '_' + - contractPermissionId.toHexString(); - - assert.fieldEquals( - 'ContractPermissionId', - contractPermissionIdEntityID, - 'id', - contractPermissionIdEntityID - ); - assert.fieldEquals( - 'ContractPermissionId', - contractPermissionIdEntityID, - 'dao', - Address.fromString(DAO_ADDRESS).toHexString() - ); - assert.fieldEquals( - 'ContractPermissionId', - contractPermissionIdEntityID, - 'where', - Address.fromString(DAO_ADDRESS).toHexString() - ); - assert.fieldEquals( - 'ContractPermissionId', - contractPermissionIdEntityID, - 'permissionId', - contractPermissionId.toHexString() - ); - - // permission - let permissionEntityID = - contractPermissionIdEntityID + - '_' + - Address.fromString(CONTRACT_ADDRESS).toHexString(); - - assert.fieldEquals( - 'Permission', - permissionEntityID, - 'id', - permissionEntityID - ); - - clearStore(); -}); - -test('Run dao (handleGranted) mappings with reverted mocke call', () => { - // create event and run it's handler - let grantedEvent = createNewGrantedEvent( - contractPermissionId, - ADDRESS_ONE, - DAO_ADDRESS, - CONTRACT_ADDRESS, - ADDRESS_TWO, - DAO_ADDRESS - ); - - // launch calls - getEXECUTE_PERMISSION_IDreverted(DAO_ADDRESS); - - // handle event - handleGranted(grantedEvent); - - // checks - // contractPermissionId - let contractPermissionIdEntityID = - Address.fromString(DAO_ADDRESS).toHexString() + - '_' + - contractPermissionId.toHexString(); - - assert.fieldEquals( - 'ContractPermissionId', - contractPermissionIdEntityID, - 'id', - contractPermissionIdEntityID - ); - - // governance - let daoPluginEntityID = - Address.fromString(DAO_ADDRESS).toHexString() + - '_' + - Address.fromString(CONTRACT_ADDRESS).toHexString(); - - assert.notInStore('DaoPlugin', daoPluginEntityID); - - clearStore(); -}); - -test('Run dao (handleRevoked) mappings with mock event', () => { - // create state - let contractPermissionIdEntityID = - Address.fromString(DAO_ADDRESS).toHexString() + - '_' + - contractPermissionId.toHexString(); - // permission - let permissionEntityID = - contractPermissionIdEntityID + - '_' + - Address.fromString(CONTRACT_ADDRESS).toHexString(); - - let contractPermissionIdEntity = new ContractPermissionId( - contractPermissionIdEntityID - ); - contractPermissionIdEntity.dao = Address.fromString( - DAO_ADDRESS - ).toHexString(); - contractPermissionIdEntity.where = Address.fromString(DAO_ADDRESS); - contractPermissionIdEntity.permissionId = contractPermissionId; - contractPermissionIdEntity.save(); - - let permissionEntity = new Permission(permissionEntityID); - permissionEntity.contractPermissionId = contractPermissionIdEntity.id; - permissionEntity.dao = Address.fromString(DAO_ADDRESS).toHexString(); - permissionEntity.where = Address.fromString(CONTRACT_ADDRESS); - permissionEntity.contractPermissionId = contractPermissionId.toHexString(); - permissionEntity.who = Address.fromString(ADDRESS_ONE); - permissionEntity.actor = Address.fromString(ADDRESS_ONE); - permissionEntity.save(); - - // check state exist - assert.fieldEquals( - 'Permission', - permissionEntityID, - 'id', - permissionEntityID - ); - - // create event and run it's handler - let revokedEvent = createNewRevokedEvent( - contractPermissionId, - ADDRESS_ONE, - DAO_ADDRESS, - CONTRACT_ADDRESS, - DAO_ADDRESS - ); - - getEXECUTE_PERMISSION_ID(DAO_ADDRESS, contractPermissionId); - - // handle event - handleRevoked(revokedEvent); - - // checks - assert.fieldEquals( - 'ContractPermissionId', - contractPermissionIdEntityID, - 'id', - contractPermissionIdEntityID - ); - assert.fieldEquals( - 'ContractPermissionId', - contractPermissionIdEntityID, - 'dao', - Address.fromString(DAO_ADDRESS).toHexString() - ); - assert.fieldEquals( - 'ContractPermissionId', - contractPermissionIdEntityID, - 'where', - Address.fromString(DAO_ADDRESS).toHexString() - ); - assert.fieldEquals( - 'ContractPermissionId', - contractPermissionIdEntityID, - 'permissionId', - contractPermissionId.toHexString() - ); - - assert.notInStore('Permission', permissionEntityID); - - clearStore(); -}); diff --git a/packages/subgraph/tests/dao/utils.ts b/packages/subgraph/tests/dao/utils.ts index 8af018818..b9f275d19 100644 --- a/packages/subgraph/tests/dao/utils.ts +++ b/packages/subgraph/tests/dao/utils.ts @@ -1,20 +1,16 @@ -import {ethereum, Bytes, Address, BigInt} from '@graphprotocol/graph-ts'; -import {createMockedFunction, newMockEvent} from 'matchstick-as/assembly/index'; import {Dao} from '../../generated/schema'; - import { MetadataSet, NativeTokenDeposited, Deposited, - Granted, - Revoked, Executed, TrustedForwarderSet, - SignatureValidatorSet, StandardCallbackRegistered, CallbackReceived, - NewURI + NewURI, } from '../../generated/templates/DaoTemplateV1_0_0/DAO'; +import {ethereum, Bytes, Address, BigInt} from '@graphprotocol/graph-ts'; +import {createMockedFunction, newMockEvent} from 'matchstick-as/assembly/index'; // events @@ -109,27 +105,6 @@ export function createTrustedForwarderSetEvent( return newTrustedForwarderSetEvent; } -export function createSignatureValidatorSetEvent( - signatureValidator: string, - contractAddress: string -): SignatureValidatorSet { - let newSignatureValidatorSetEvent = changetype( - newMockEvent() - ); - - newSignatureValidatorSetEvent.address = Address.fromString(contractAddress); - newSignatureValidatorSetEvent.parameters = []; - - let trustedForwarderParam = new ethereum.EventParam( - 'signatureValidator', - ethereum.Value.fromAddress(Address.fromString(signatureValidator)) - ); - - newSignatureValidatorSetEvent.parameters.push(trustedForwarderParam); - - return newSignatureValidatorSetEvent; -} - export function createNewNativeTokenDepositedEvent( sender: string, amount: string, @@ -192,86 +167,6 @@ export function createNewDepositedEvent( return newEvent; } -export function createNewGrantedEvent( - contractPermissionId: Bytes, - actor: string, - where: string, - who: string, - condition: string, - contractAddress: string -): Granted { - let newGrantedEvent = changetype(newMockEvent()); - - newGrantedEvent.address = Address.fromString(contractAddress); - newGrantedEvent.parameters = []; - - let contractPermissionIdParam = new ethereum.EventParam( - 'contractPermissionId', - ethereum.Value.fromBytes(contractPermissionId) - ); - let actorParam = new ethereum.EventParam( - 'actor', - ethereum.Value.fromAddress(Address.fromString(actor)) - ); - let whereParam = new ethereum.EventParam( - 'where', - ethereum.Value.fromAddress(Address.fromString(where)) - ); - let whoParam = new ethereum.EventParam( - 'who', - ethereum.Value.fromAddress(Address.fromString(who)) - ); - let conditionParam = new ethereum.EventParam( - 'condition', - ethereum.Value.fromAddress(Address.fromString(condition)) - ); - - newGrantedEvent.parameters.push(contractPermissionIdParam); - newGrantedEvent.parameters.push(actorParam); - newGrantedEvent.parameters.push(whereParam); - newGrantedEvent.parameters.push(whoParam); - newGrantedEvent.parameters.push(conditionParam); - - return newGrantedEvent; -} - -export function createNewRevokedEvent( - contractPermissionId: Bytes, - actor: string, - where: string, - who: string, - contractAddress: string -): Revoked { - let newGrantedEvent = changetype(newMockEvent()); - - newGrantedEvent.address = Address.fromString(contractAddress); - newGrantedEvent.parameters = []; - - let contractPermissionIdParam = new ethereum.EventParam( - 'contractPermissionId', - ethereum.Value.fromBytes(contractPermissionId) - ); - let actorParam = new ethereum.EventParam( - 'actor', - ethereum.Value.fromAddress(Address.fromString(actor)) - ); - let whereParam = new ethereum.EventParam( - 'where', - ethereum.Value.fromAddress(Address.fromString(where)) - ); - let whoParam = new ethereum.EventParam( - 'who', - ethereum.Value.fromAddress(Address.fromString(who)) - ); - - newGrantedEvent.parameters.push(contractPermissionIdParam); - newGrantedEvent.parameters.push(actorParam); - newGrantedEvent.parameters.push(whereParam); - newGrantedEvent.parameters.push(whoParam); - - return newGrantedEvent; -} - export function createNewExecutedEvent( actor: string, callId: string, @@ -480,7 +375,7 @@ export function getIsUserAllowed( ) .withArgs([ ethereum.Value.fromAddress(Address.fromString(address)), - ethereum.Value.fromUnsignedBigInt(BigInt.zero()) + ethereum.Value.fromUnsignedBigInt(BigInt.zero()), ]) .returns([ethereum.Value.fromBoolean(returns)]); } @@ -509,7 +404,7 @@ export function getSupportsInterface( 'supportsInterface(bytes4):(bool)' ) .withArgs([ - ethereum.Value.fromFixedBytes(Bytes.fromHexString(interfaceId) as Bytes) + ethereum.Value.fromFixedBytes(Bytes.fromHexString(interfaceId) as Bytes), ]) .returns([ethereum.Value.fromBoolean(returns)]); } diff --git a/packages/subgraph/tests/helpers/method-classes.ts b/packages/subgraph/tests/helpers/method-classes.ts index 0c3eae48f..781a00f0a 100644 --- a/packages/subgraph/tests/helpers/method-classes.ts +++ b/packages/subgraph/tests/helpers/method-classes.ts @@ -2,8 +2,6 @@ * IMPORTANT: Do not export classes from this file. * The classes of this file are meant to be incorporated into the classes of ./extended-schema.ts */ - -import {Address, BigInt, Bytes, ethereum} from '@graphprotocol/graph-ts'; import { Dao, ERC20Balance, @@ -23,36 +21,38 @@ import { TokenVotingPlugin, TokenVotingProposal, TokenVotingVote, - TokenVotingVoter + TokenVotingVoter, + Permission, } from '../../generated/schema'; import { CallbackReceived, Deposited, NativeTokenDeposited, - NewURI + NewURI, } from '../../generated/templates/DaoTemplateV1_0_0/DAO'; import { DelegateChanged, - DelegateVotesChanged + DelegateVotesChanged, } from '../../generated/templates/GovernanceERC20/GovernanceERC20'; import { MembershipContractAnnounced, ProposalCreated, ProposalExecuted, VoteCast, - VotingSettingsUpdated + VotingSettingsUpdated, } from '../../generated/templates/TokenVoting/TokenVoting'; import { VOTER_OPTIONS, VOTE_OPTIONS, VOTING_MODES, - VOTING_MODE_INDEXES + VOTING_MODE_INDEXES, } from '../../src/utils/constants'; +import {generateMemberEntityId} from '../../src/utils/ids'; import { getBalanceId, getERC1155TransferId, getTokenIdBalanceId, - getTransferId + getTransferId, } from '../../src/utils/tokens/common'; import { ADDRESS_ONE, @@ -77,7 +77,8 @@ import { STRING_DATA, ADDRESS_TWO, ADDRESS_THREE, - ADDRESS_ZERO + ADDRESS_ZERO, + ADDRESS_FOUR, } from '../constants'; import { createCallbackReceivedEvent, @@ -85,8 +86,12 @@ import { createNewNativeTokenDepositedEvent, createNewURIEvent, getBalanceOf, - getSupportsInterface + getSupportsInterface, } from '../dao/utils'; +import { + createNewGrantedEvent, + createNewRevokedEvent, +} from '../permission-manager/utils'; import { createNewDelegateChangedEvent, createNewDelegateVotesChangedEvent, @@ -95,17 +100,103 @@ import { createNewProposalExecutedEvent, createNewVoteCastEvent, createNewVotingSettingsUpdatedEvent, - getProposalCountCall + delegatesCall, + getProposalCountCall, } from '../token/utils'; +import {createGetProposalCall, createTotalVotingPowerCall} from '../utils'; import { - createGetProposalCall, - createTotalVotingPowerCall, + generateEntityIdFromAddress, + generatePermissionEntityId, + generatePluginEntityId, + createERC20TokenCalls, + createERC1155TokenCalls, + createWrappedERC20TokenCalls, createTokenCalls, - createWrappedTokenCalls, - createERC1155TokenCalls -} from '../utils'; +} from '@aragon/osx-commons-subgraph'; +import { + Address, + BigInt, + ByteArray, + Bytes, + crypto, + ethereum, +} from '@graphprotocol/graph-ts'; /* eslint-disable @typescript-eslint/no-unused-vars */ +// PermissionManager +class PermissionMethods extends Permission { + withDefaultValues( + emittingContract: string = Address.fromString( + CONTRACT_ADDRESS + ).toHexString() + ): PermissionMethods { + const permissionId = Bytes.fromByteArray( + crypto.keccak256(ByteArray.fromUTF8('EXECUTE_PERMISSION')) + ); + + const emittingContractAddress = Address.fromString(emittingContract); + const whereAddress = Address.fromString(ADDRESS_ONE); + const whoAddress = Address.fromString(ADDRESS_TWO); + const actorAddress = Address.fromString(ADDRESS_THREE); + const conditionAddress = Address.fromString(ADDRESS_FOUR); + + this.id = generatePermissionEntityId( + emittingContractAddress, + permissionId, + whereAddress, + whoAddress + ); + this.where = whereAddress; + this.permissionId = permissionId; + this.who = whoAddress; + this.actor = actorAddress; + this.condition = conditionAddress; + + this.dao = null; + this.pluginRepo = null; + + return this; + } + + // events + createEvent_Granted( + emittingContract: string = Address.fromString( + CONTRACT_ADDRESS + ).toHexString() + ): T { + if (this.condition === null) { + throw new Error('Condition is null'); + } + + let event = createNewGrantedEvent( + this.permissionId, + this.actor.toHexString(), + this.where.toHexString(), + this.who.toHexString(), + (this.condition as Bytes).toHexString(), + emittingContract + ); + + return event as T; + } + + createEvent_Revoked( + emittingContract: string = Address.fromString( + CONTRACT_ADDRESS + ).toHexString() + ): T { + let event = createNewRevokedEvent( + this.permissionId, + this.actor.toHexString(), + this.where.toHexString(), + this.who.toHexString(), + emittingContract + ); + + return event as T; + } +} + // ERC1155Contract class ERC1155ContractMethods extends ERC1155Contract { withDefaultValues(): ERC1155ContractMethods { @@ -182,13 +273,11 @@ class ERC721ContractMethods extends ERC721Contract { if (!this.symbol) { throw new Error('Symbol is null'); } - // we cast to string only for stoping rust compiler complaints. + // we cast to string only for stopping rust compiler complaints. createTokenCalls( - this.id, + DAO_TOKEN_ADDRESS, this.name as string, - this.symbol as string, - null, - null + this.symbol as string ); } } @@ -235,9 +324,8 @@ class ERC20WrapperContractMethods extends ERC20WrapperContract { this.name = 'Wrapped Test Token'; this.symbol = 'WTT'; this.decimals = 18; - this.underlyingToken = Address.fromHexString( - DAO_TOKEN_ADDRESS - ).toHexString(); + this.underlyingToken = + Address.fromHexString(DAO_TOKEN_ADDRESS).toHexString(); return this; } // calls @@ -249,13 +337,17 @@ class ERC20WrapperContractMethods extends ERC20WrapperContract { } else if (!this.underlyingToken) { throw new Error('Underlying token is null'); } + let supply = '10'; + if (totalSupply) { + supply = totalSupply; + } - createWrappedTokenCalls( + createWrappedERC20TokenCalls( this.id, - this.name as string, - this.symbol as string, this.underlyingToken, - totalSupply + supply, + this.name as string, + this.symbol as string ); } @@ -286,13 +378,17 @@ class ERC20ContractMethods extends ERC20Contract { if (!this.symbol) { throw new Error('Symbol is null'); } + let supply = '10'; + if (totalSupply) { + supply = totalSupply; + } // we cast to string only for stoping rust compiler complaints. - createTokenCalls( + createERC20TokenCalls( this.id, + supply, this.name as string, this.symbol as string, - this.decimals.toString(), - totalSupply + this.decimals.toString() ); } @@ -451,12 +547,13 @@ class DaoMethods extends Dao { // Token Voting class TokenVotingVoterMethods extends TokenVotingVoter { withDefaultValues(): TokenVotingVoterMethods { - this.id = Address.fromHexString(CONTRACT_ADDRESS) - .toHexString() - .concat('_') - .concat(ADDRESS_ONE); - this.address = ADDRESS_ONE; - this.plugin = Address.fromHexString(CONTRACT_ADDRESS).toHexString(); + const memberAddress = Address.fromString(ADDRESS_ONE); + const memberId = generateEntityIdFromAddress(memberAddress); + const pluginAddress = Address.fromString(CONTRACT_ADDRESS); + const pluginEntityId = generatePluginEntityId(pluginAddress); + this.id = generateMemberEntityId(pluginAddress, memberAddress); + this.address = memberId; + this.plugin = pluginEntityId; this.lastUpdated = BigInt.zero(); return this; @@ -491,7 +588,8 @@ class TokenVotingProposalMethods extends TokenVotingProposal { this.allowFailureMap = BigInt.fromString(ALLOW_FAILURE_MAP); this.createdAt = BigInt.fromString(CREATED_AT); this.creationBlockNumber = BigInt.fromString(ZERO); - this.potentiallyExecutable = false; + this.approvalReached = false; + this.isSignaling = false; return this; } @@ -622,10 +720,10 @@ class TokenVotingPluginMethods extends TokenVotingPlugin { // we use casting here to remove autocompletion complaint // since we know it will be captured by the previous check let votingMode = VOTING_MODES.get(votingModeIndex) as string; + const pluginAddress = Address.fromString(CONTRACT_ADDRESS); + const pluginEntityId = generatePluginEntityId(pluginAddress); - const pluginAddress = Address.fromHexString(CONTRACT_ADDRESS); - - this.id = pluginAddress.toHexString(); + this.id = pluginEntityId; this.dao = DAO_ADDRESS; this.pluginAddress = pluginAddress; this.votingMode = votingMode; @@ -692,23 +790,32 @@ class TokenVotingMemberMethods extends TokenVotingMember { memberAddress: string = ADDRESS_ONE, pluginAddress: string = CONTRACT_ADDRESS ): TokenVotingMemberMethods { - const plugin = Address.fromHexString(pluginAddress); - let id = memberAddress.concat('_').concat(plugin.toHexString()); + const plugin = Address.fromBytes(Bytes.fromHexString(pluginAddress)); + const member = Address.fromBytes(Bytes.fromHexString(memberAddress)); + let id = generateMemberEntityId(plugin, member); this.id = id; this.address = Address.fromHexString(memberAddress); this.balance = BigInt.zero(); this.plugin = plugin.toHexString(); - this.delegatee = id; + this.delegatee = null; this.votingPower = BigInt.zero(); return this; } + mockCall_delegatesCall( + tokenContractAddress: string, + account: string, + returns: string + ): void { + delegatesCall(tokenContractAddress, account, returns); + } + createEvent_DelegateChanged( delegator: string = this.address.toHexString(), - fromDelegate: string = ADDRESS_ONE, - toDelegate: string = ADDRESS_ONE, + fromDelegate: string = this.address.toHexString(), + toDelegate: string = this.address.toHexString(), tokenContract: string = Address.fromHexString( DAO_TOKEN_ADDRESS ).toHexString() diff --git a/packages/subgraph/tests/multisig/multisig.test.ts b/packages/subgraph/tests/multisig/multisig.test.ts index 7f012ceb5..f60ff4bb7 100644 --- a/packages/subgraph/tests/multisig/multisig.test.ts +++ b/packages/subgraph/tests/multisig/multisig.test.ts @@ -1,15 +1,16 @@ -import {assert, clearStore, test} from 'matchstick-as/assembly/index'; -import {Address, BigInt} from '@graphprotocol/graph-ts'; - +import {MultisigApprover} from '../../generated/schema'; import { handleMembersAdded, handleApproved, handleProposalExecuted, handleMembersRemoved, _handleProposalCreated, - handleMultisigSettingsUpdated + handleMultisigSettingsUpdated, } from '../../src/packages/multisig/multisig'; -import {MultisigApprover} from '../../generated/schema'; +import { + generateMemberEntityId, + generateVoterEntityId, +} from '../../src/utils/ids'; import { ADDRESS_ONE, ADDRESS_TWO, @@ -24,9 +25,8 @@ import { PROPOSAL_ENTITY_ID, START_DATE, END_DATE, - ALLOW_FAILURE_MAP + ALLOW_FAILURE_MAP, } from '../constants'; -import {createDummyActions} from '../utils'; import { createNewMembersAddedEvent, createNewApprovedEvent, @@ -37,11 +37,21 @@ import { createMultisigProposalEntityState, createGetProposalCall, createNewMultisigSettingsUpdatedEvent, - createMultisigPluginState + createMultisigPluginState, } from './utils'; -import {getProposalId} from '../../src/utils/proposals'; +import { + generatePluginEntityId, + generateProposalEntityId, + createDummyAction, +} from '@aragon/osx-commons-subgraph'; +import {Address, BigInt} from '@graphprotocol/graph-ts'; +import {assert, clearStore, test} from 'matchstick-as/assembly/index'; + +let actions = [createDummyAction(DAO_TOKEN_ADDRESS, '0', '0x00000000')]; -let actions = createDummyActions(DAO_TOKEN_ADDRESS, '0', '0x00000000'); +const pluginAddress = Address.fromString(CONTRACT_ADDRESS); +const pluginEntityId = generatePluginEntityId(pluginAddress); +const pluginProposalId = BigInt.fromString(PLUGIN_PROPOSAL_ID); test('Run Multisig (handleProposalCreated) mappings with mock event', () => { // create state @@ -83,51 +93,74 @@ test('Run Multisig (handleProposalCreated) mappings with mock event', () => { // handle event _handleProposalCreated(event, DAO_ADDRESS, STRING_DATA); - let entityID = getProposalId( - Address.fromString(CONTRACT_ADDRESS), - BigInt.fromString(PLUGIN_PROPOSAL_ID) + let proposalEntityId = generateProposalEntityId( + pluginAddress, + pluginProposalId ); - let packageId = Address.fromString(CONTRACT_ADDRESS).toHexString(); - // checks - assert.fieldEquals('MultisigProposal', entityID, 'id', entityID); - assert.fieldEquals('MultisigProposal', entityID, 'dao', DAO_ADDRESS); - assert.fieldEquals('MultisigProposal', entityID, 'plugin', packageId); assert.fieldEquals( 'MultisigProposal', - entityID, + proposalEntityId, + 'id', + proposalEntityId + ); + assert.fieldEquals('MultisigProposal', proposalEntityId, 'dao', DAO_ADDRESS); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'plugin', + pluginEntityId + ); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, 'pluginProposalId', PLUGIN_PROPOSAL_ID ); - assert.fieldEquals('MultisigProposal', entityID, 'creator', ADDRESS_ONE); - assert.fieldEquals('MultisigProposal', entityID, 'startDate', START_DATE); - assert.fieldEquals('MultisigProposal', entityID, 'endDate', END_DATE); - assert.fieldEquals('MultisigProposal', entityID, 'metadata', STRING_DATA); assert.fieldEquals( 'MultisigProposal', - entityID, + proposalEntityId, + 'creator', + ADDRESS_ONE + ); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'startDate', + START_DATE + ); + assert.fieldEquals('MultisigProposal', proposalEntityId, 'endDate', END_DATE); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'metadata', + STRING_DATA + ); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, 'createdAt', event.block.timestamp.toString() ); assert.fieldEquals( 'MultisigProposal', - entityID, + proposalEntityId, 'creationBlockNumber', event.block.number.toString() ); assert.fieldEquals( 'MultisigProposal', - entityID, + proposalEntityId, 'snapshotBlock', SNAPSHOT_BLOCK ); - assert.fieldEquals('MultisigProposal', entityID, 'minApprovals', ONE); - assert.fieldEquals('MultisigProposal', entityID, 'approvals', ONE); - assert.fieldEquals('MultisigProposal', entityID, 'executed', 'false'); + assert.fieldEquals('MultisigProposal', proposalEntityId, 'minApprovals', ONE); + assert.fieldEquals('MultisigProposal', proposalEntityId, 'approvals', ONE); + assert.fieldEquals('MultisigProposal', proposalEntityId, 'executed', 'false'); assert.fieldEquals( 'MultisigProposal', - entityID, + proposalEntityId, 'allowFailureMap', ALLOW_FAILURE_MAP ); @@ -181,33 +214,33 @@ test('Run Multisig (handleApproved) mappings with mock event', () => { handleApproved(event); // checks - const member = Address.fromString(ADDRESS_ONE).toHexString(); - const pluginId = Address.fromString(CONTRACT_ADDRESS).toHexString(); - const memberId = pluginId + '_' + member; + const memberAddress = Address.fromString(ADDRESS_ONE); + + const memberEntityId = generateMemberEntityId(pluginAddress, memberAddress); + const voterEntityId = generateVoterEntityId(memberEntityId, proposal.id); // check proposalVoter - let proposalVoterId = member + '_' + proposal.id; assert.fieldEquals( 'MultisigProposalApprover', - proposalVoterId, + voterEntityId, 'id', - proposalVoterId + voterEntityId ); assert.fieldEquals( 'MultisigProposalApprover', - proposalVoterId, + voterEntityId, 'approver', - memberId + memberEntityId ); assert.fieldEquals( 'MultisigProposalApprover', - proposalVoterId, + voterEntityId, 'proposal', proposal.id ); assert.fieldEquals( 'MultisigProposalApprover', - proposalVoterId, + voterEntityId, 'createdAt', event.block.timestamp.toString() ); @@ -217,7 +250,7 @@ test('Run Multisig (handleApproved) mappings with mock event', () => { assert.fieldEquals( 'MultisigProposal', proposal.id, - 'potentiallyExecutable', + 'approvalReached', 'false' ); @@ -255,7 +288,7 @@ test('Run Multisig (handleApproved) mappings with mock event', () => { assert.fieldEquals( 'MultisigProposal', proposal.id, - 'potentiallyExecutable', + 'approvalReached', 'true' ); @@ -315,7 +348,7 @@ test('Run Multisig (handleProposalExecuted) mappings with mock event', () => { test('Run Multisig (handleMembersAdded) mappings with mock event', () => { let userArray = [ Address.fromString(ADDRESS_ONE), - Address.fromString(ADDRESS_TWO) + Address.fromString(ADDRESS_TWO), ]; // create event @@ -351,7 +384,7 @@ test('Run Multisig (handleMembersRemoved) mappings with mock event', () => { // create state let memberAddresses = [ Address.fromString(ADDRESS_ONE), - Address.fromString(ADDRESS_TWO) + Address.fromString(ADDRESS_TWO), ]; for (let index = 0; index < memberAddresses.length; index++) { diff --git a/packages/subgraph/tests/multisig/utils.ts b/packages/subgraph/tests/multisig/utils.ts index 2edd440e6..2f7883477 100644 --- a/packages/subgraph/tests/multisig/utils.ts +++ b/packages/subgraph/tests/multisig/utils.ts @@ -1,14 +1,11 @@ -import {Address, BigInt, Bytes, ethereum} from '@graphprotocol/graph-ts'; -import {createMockedFunction, newMockEvent} from 'matchstick-as'; import {MultisigPlugin, MultisigProposal} from '../../generated/schema'; - import { ProposalCreated, Approved, ProposalExecuted, MembersAdded, MembersRemoved, - MultisigSettingsUpdated + MultisigSettingsUpdated, } from '../../generated/templates/Multisig/Multisig'; import { ADDRESS_ONE, @@ -24,8 +21,10 @@ import { ALLOW_FAILURE_MAP, ZERO, THREE, - PLUGIN_ENTITY_ID + PLUGIN_ENTITY_ID, } from '../constants'; +import {Address, BigInt, Bytes, ethereum} from '@graphprotocol/graph-ts'; +import {createMockedFunction, newMockEvent} from 'matchstick-as'; // events @@ -247,7 +246,7 @@ export function createGetProposalCall( 'getProposal(uint256):(bool,uint16,(uint16,uint64,uint64,uint64),(address,uint256,bytes)[],uint256)' ) .withArgs([ - ethereum.Value.fromUnsignedBigInt(BigInt.fromString(proposalId)) + ethereum.Value.fromUnsignedBigInt(BigInt.fromString(proposalId)), ]) .returns([ ethereum.Value.fromBoolean(executed), @@ -259,7 +258,7 @@ export function createGetProposalCall( ethereum.Value.fromTupleArray(actions), - ethereum.Value.fromUnsignedBigInt(BigInt.fromString(allowFailureMap)) + ethereum.Value.fromUnsignedBigInt(BigInt.fromString(allowFailureMap)), ]); } @@ -309,7 +308,8 @@ export function createMultisigProposalEntityState( multisigProposal.creator = Address.fromString(creator); multisigProposal.startDate = BigInt.fromString(startDate); multisigProposal.endDate = BigInt.fromString(endDate); - multisigProposal.potentiallyExecutable = executable; + multisigProposal.approvalReached = executable; + multisigProposal.isSignaling = false; multisigProposal.executed = executed; multisigProposal.snapshotBlock = BigInt.fromString(snapshotBlock); multisigProposal.minApprovals = BigInt.fromString(minApprovals).toI32(); diff --git a/packages/subgraph/tests/permission-manager/permission-manager.test.ts b/packages/subgraph/tests/permission-manager/permission-manager.test.ts new file mode 100644 index 000000000..e8e1ddc71 --- /dev/null +++ b/packages/subgraph/tests/permission-manager/permission-manager.test.ts @@ -0,0 +1,108 @@ +import { + Granted as DaoGrantedEvent, + Revoked as DaoRevokedEvent, +} from '../../generated/templates/DaoTemplateV1_0_0/DAO'; +import { + Granted as RepoGrantedEvent, + Revoked as RepoRevokedEvent, +} from '../../generated/templates/PluginRepoTemplate/PluginRepo'; +import { + handleGranted as daoHandleGranted, + handleRevoked as daoHandleRevoked, +} from '../../src/dao/dao_v1_0_0'; +import { + handleGranted as repoHandleGranted, + handleRevoked as repoHandleRevoked, +} from '../../src/plugin/pluginRepo'; +import {CONTRACT_ADDRESS, DAO_ADDRESS} from '../constants'; +import {ExtendedPermission} from '../helpers/extended-schema'; +import {generateDaoEntityId} from '@aragon/osx-commons-subgraph'; +import {Address} from '@graphprotocol/graph-ts'; +import {assert, clearStore, test} from 'matchstick-as/assembly/index'; + +const daoAddress = Address.fromString(DAO_ADDRESS); +const pluginRepoAddress = Address.fromString(CONTRACT_ADDRESS); +const daoEntityId = generateDaoEntityId(daoAddress); +const pluginRepoEntityId = generateDaoEntityId(pluginRepoAddress); + +// DAO +test('Run dao (handleGranted) mappings with mock event', () => { + let permission = new ExtendedPermission().withDefaultValues(daoEntityId); + permission.dao = daoEntityId; + + let grantedEvent = + permission.createEvent_Granted(daoEntityId); + + // handle event + daoHandleGranted(grantedEvent); + + // checks + permission.assertEntity(); + + clearStore(); +}); + +test('Run dao (handleRevoked) mappings with mock event', () => { + let permission = new ExtendedPermission().withDefaultValues(daoEntityId); + permission.dao = daoEntityId; + + permission.save(); + + // check state exist + permission.assertEntity(); + + // create event and run it's handler + let revokedEvent = + permission.createEvent_Revoked(daoEntityId); + + // handle event + daoHandleRevoked(revokedEvent); + + // checks + assert.notInStore('Permission', permission.id); + + clearStore(); +}); + +// PluginRepo +test('Run PluginRepo (handleGranted) mappings with mock event', () => { + let permission = new ExtendedPermission().withDefaultValues( + pluginRepoEntityId + ); + permission.pluginRepo = pluginRepoEntityId; + + let grantedEvent = + permission.createEvent_Granted(pluginRepoEntityId); + + // handle event + repoHandleGranted(grantedEvent); + + // checks + permission.assertEntity(); + + clearStore(); +}); + +test('Run PluginRepo (handleRevoked) mappings with mock event', () => { + let permission = new ExtendedPermission().withDefaultValues( + pluginRepoEntityId + ); + permission.pluginRepo = pluginRepoEntityId; + + permission.save(); + + // check state exist + permission.assertEntity(); + + // create event and run it's handler + let revokedEvent = + permission.createEvent_Revoked(pluginRepoEntityId); + + // handle event + repoHandleRevoked(revokedEvent); + + // checks + assert.notInStore('Permission', permission.id); + + clearStore(); +}); diff --git a/packages/subgraph/tests/permission-manager/utils.ts b/packages/subgraph/tests/permission-manager/utils.ts new file mode 100644 index 000000000..d9c7881b9 --- /dev/null +++ b/packages/subgraph/tests/permission-manager/utils.ts @@ -0,0 +1,82 @@ +import {Address, Bytes, ethereum} from '@graphprotocol/graph-ts'; +import {newMockEvent} from 'matchstick-as'; + +export function createNewGrantedEvent( + contractPermissionId: Bytes, + actor: string, + where: string, + who: string, + condition: string, + contractAddress: string +): T { + let newGrantedEvent = changetype(newMockEvent()); + + newGrantedEvent.address = Address.fromString(contractAddress); + newGrantedEvent.parameters = []; + + let contractPermissionIdParam = new ethereum.EventParam( + 'contractPermissionId', + ethereum.Value.fromBytes(contractPermissionId) + ); + let actorParam = new ethereum.EventParam( + 'actor', + ethereum.Value.fromAddress(Address.fromString(actor)) + ); + let whereParam = new ethereum.EventParam( + 'where', + ethereum.Value.fromAddress(Address.fromString(where)) + ); + let whoParam = new ethereum.EventParam( + 'who', + ethereum.Value.fromAddress(Address.fromString(who)) + ); + let conditionParam = new ethereum.EventParam( + 'condition', + ethereum.Value.fromAddress(Address.fromString(condition)) + ); + + newGrantedEvent.parameters.push(contractPermissionIdParam); + newGrantedEvent.parameters.push(actorParam); + newGrantedEvent.parameters.push(whereParam); + newGrantedEvent.parameters.push(whoParam); + newGrantedEvent.parameters.push(conditionParam); + + return newGrantedEvent as T; +} + +export function createNewRevokedEvent( + contractPermissionId: Bytes, + actor: string, + where: string, + who: string, + contractAddress: string +): T { + let newGrantedEvent = changetype(newMockEvent()); + + newGrantedEvent.address = Address.fromString(contractAddress); + newGrantedEvent.parameters = []; + + let contractPermissionIdParam = new ethereum.EventParam( + 'contractPermissionId', + ethereum.Value.fromBytes(contractPermissionId) + ); + let actorParam = new ethereum.EventParam( + 'actor', + ethereum.Value.fromAddress(Address.fromString(actor)) + ); + let whereParam = new ethereum.EventParam( + 'where', + ethereum.Value.fromAddress(Address.fromString(where)) + ); + let whoParam = new ethereum.EventParam( + 'who', + ethereum.Value.fromAddress(Address.fromString(who)) + ); + + newGrantedEvent.parameters.push(contractPermissionIdParam); + newGrantedEvent.parameters.push(actorParam); + newGrantedEvent.parameters.push(whereParam); + newGrantedEvent.parameters.push(whoParam); + + return newGrantedEvent as T; +} diff --git a/packages/subgraph/tests/plugin/pluginRepo.test.ts b/packages/subgraph/tests/plugin/pluginRepo.test.ts index 31f0e31b7..25f82eb7d 100644 --- a/packages/subgraph/tests/plugin/pluginRepo.test.ts +++ b/packages/subgraph/tests/plugin/pluginRepo.test.ts @@ -1,11 +1,16 @@ -import {assert, clearStore, test} from 'matchstick-as/assembly/index'; -import {ADDRESS_ONE, ONE} from '../constants'; -import {createReleaseMetadataUpdatedEvent, createVersionCreated} from './utils'; import { handleReleaseMetadataUpdated, - handleVersionCreated + handleVersionCreated, } from '../../src/plugin/pluginRepo'; +import {ADDRESS_ONE, ONE} from '../constants'; +import {createReleaseMetadataUpdatedEvent, createVersionCreated} from './utils'; +import { + generatePluginReleaseEntityId, + generatePluginRepoEntityId, + generatePluginVersionEntityId, +} from '@aragon/osx-commons-subgraph'; import {Bytes} from '@graphprotocol/graph-ts'; +import {assert, clearStore, test} from 'matchstick-as/assembly/index'; test('PluginRepo (handleVersionCreated) mappings with mock event', () => { let release = ONE; @@ -22,40 +27,46 @@ test('PluginRepo (handleVersionCreated) mappings with mock event', () => { handleVersionCreated(event); - let pluginRepoId = event.address.toHexString(); - - let pluginVersionId = pluginRepoId - .concat('_') - .concat(release) - .concat('_') - .concat(build); + let pluginVersionEntityId = generatePluginVersionEntityId( + event.address, + parseInt(release) as i32, + parseInt(build) as i32 + ); - let pluginReleaseId = pluginRepoId.concat('_').concat(release); + let pluginReleaseEntityId = generatePluginReleaseEntityId( + event.address, + parseInt(release) as i32 + ); assert.entityCount('PluginVersion', 1); - assert.fieldEquals('PluginVersion', pluginVersionId, 'id', pluginVersionId); assert.fieldEquals( 'PluginVersion', - pluginVersionId, + pluginVersionEntityId, + 'id', + pluginVersionEntityId + ); + assert.fieldEquals( + 'PluginVersion', + pluginVersionEntityId, 'pluginRepo', event.address.toHexString() ); assert.fieldEquals( 'PluginVersion', - pluginVersionId, + pluginVersionEntityId, 'release', - pluginReleaseId + pluginReleaseEntityId ); - assert.fieldEquals('PluginVersion', pluginVersionId, 'build', build); + assert.fieldEquals('PluginVersion', pluginVersionEntityId, 'build', build); assert.fieldEquals( 'PluginVersion', - pluginVersionId, + pluginVersionEntityId, 'pluginSetup', pluginSetup ); assert.fieldEquals( 'PluginVersion', - pluginVersionId, + pluginVersionEntityId, 'metadata', buildMetadata ); @@ -79,8 +90,11 @@ test('PluginRepo (handleReleaseMetadataUpdated) mappings with mock event', () => assert.entityCount('PluginRelease', 1); - let pluginRepoId = event.address.toHexString(); - let pluginReleaseEntityId = pluginRepoId.concat('_').concat(release); + let pluginRepoEntityId = generatePluginRepoEntityId(event.address); + let pluginReleaseEntityId = generatePluginReleaseEntityId( + event.address, + parseInt(release) as i32 + ); assert.fieldEquals( 'PluginRelease', @@ -92,7 +106,7 @@ test('PluginRepo (handleReleaseMetadataUpdated) mappings with mock event', () => 'PluginRelease', pluginReleaseEntityId, 'pluginRepo', - pluginRepoId + pluginRepoEntityId ); assert.fieldEquals( 'PluginRelease', diff --git a/packages/subgraph/tests/plugin/pluginSetupProcessor.test.ts b/packages/subgraph/tests/plugin/pluginSetupProcessor.test.ts index a02043e33..e68a2bb87 100644 --- a/packages/subgraph/tests/plugin/pluginSetupProcessor.test.ts +++ b/packages/subgraph/tests/plugin/pluginSetupProcessor.test.ts @@ -1,5 +1,21 @@ +import {PluginPreparation} from '../../generated/schema'; +import { + handleInstallationApplied, + handleInstallationPrepared, + handleUninstallationApplied, + handleUninstallationPrepared, + handleUpdateApplied, + handleUpdatePrepared, +} from '../../src/plugin/pluginSetupProcessor'; +import {PERMISSION_OPERATIONS} from '../../src/plugin/utils'; +import { + ADMIN_INTERFACE_ID, + TOKEN_VOTING_INTERFACE_ID, + ADDRESSLIST_VOTING_INTERFACE_ID, + MULTISIG_INTERFACE_ID, +} from '../../src/utils/constants'; +import {getSupportsInterface} from '../../tests/dao/utils'; import { - ADDRESS_ONE, ADDRESS_TWO, ADDRESS_THREE, DAO_ADDRESS, @@ -9,7 +25,7 @@ import { PLUGIN_SETUP_ID, ADDRESS_SIX, APPLIED_PLUGIN_SETUP_ID, - CONTRACT_ADDRESS + CONTRACT_ADDRESS, } from '../constants'; import { createInstallationAppliedEvent, @@ -17,44 +33,42 @@ import { createUninstallationAppliedEvent, createUninstallationPreparedEvent, createUpdateAppliedEvent, - createUpdatePreparedEvent + createUpdatePreparedEvent, } from './utils'; import { - handleInstallationApplied, - handleInstallationPrepared, - handleUninstallationApplied, - handleUninstallationPrepared, - handleUpdateApplied, - handleUpdatePrepared -} from '../../src/plugin/pluginSetupProcessor'; -import {assert, clearStore, test} from 'matchstick-as'; -import {PluginPreparation} from '../../generated/schema'; + generateDaoEntityId, + generatePluginEntityId, + generatePluginInstallationEntityId, + generatePluginPermissionEntityId, + generatePluginPreparationEntityId, + generatePluginRepoEntityId, + generatePluginVersionEntityId, +} from '@aragon/osx-commons-subgraph'; import {Address, BigInt, Bytes, ethereum} from '@graphprotocol/graph-ts'; -import {getSupportsInterface} from '../../tests/dao/utils'; -import { - ADDRESSLIST_VOTING_INTERFACE, - ADMIN_INTERFACE, - MULTISIG_INTERFACE, - TOKEN_VOTING_INTERFACE -} from '../../src/utils/constants'; -import { - getPluginInstallationId, - PERMISSION_OPERATIONS -} from '../../src/plugin/utils'; - -test('InstallationPrepared event', function() { - let dao = DAO_ADDRESS; - let plugin = CONTRACT_ADDRESS; - let setupId = PLUGIN_SETUP_ID; - let pluginSetupRepo = ADDRESS_TWO; - let pluginVersionId = `${pluginSetupRepo}_1_1`; - let installationId = getPluginInstallationId(dao, plugin); - if (!installationId) { +import {assert, clearStore, test} from 'matchstick-as'; + +const daoAddress = Address.fromString(DAO_ADDRESS); +const daoEntityId = generateDaoEntityId(daoAddress); +const pluginAddress = Address.fromString(CONTRACT_ADDRESS); +const pluginEntityId = generatePluginEntityId(pluginAddress); +const setupId = PLUGIN_SETUP_ID; +const pluginRepoAddress = Address.fromString(ADDRESS_TWO); +const pluginRepoEntityId = generatePluginRepoEntityId(pluginRepoAddress); + +test('InstallationPrepared event', function () { + let pluginVersionId = generatePluginVersionEntityId(pluginRepoAddress, 1, 1); + let pluginInstallationEntityId = generatePluginInstallationEntityId( + daoAddress, + pluginAddress + ); + if (!pluginInstallationEntityId) { throw new Error('Failed to get installationId'); } - - let installationIdString = installationId.toHexString(); - let preparationId = `${installationIdString}_${setupId}`; + pluginInstallationEntityId = pluginInstallationEntityId as string; + let pluginPreparationEntityId = generatePluginPreparationEntityId( + pluginInstallationEntityId, + Bytes.fromHexString(setupId) + ); let versionTuple = new ethereum.Tuple(); versionTuple.push(ethereum.Value.fromSignedBigInt(BigInt.fromString('1'))); @@ -63,32 +77,32 @@ test('InstallationPrepared event', function() { let permissions = [ [ ethereum.Value.fromSignedBigInt(BigInt.fromString('0')), - ethereum.Value.fromAddress(Address.fromString(dao)), - ethereum.Value.fromAddress(Address.fromString(plugin)), + ethereum.Value.fromAddress(daoAddress), + ethereum.Value.fromAddress(pluginAddress), ethereum.Value.fromAddress(Address.fromString(ADDRESS_ZERO)), - ethereum.Value.fromBytes(Bytes.fromHexString('0x1234')) + ethereum.Value.fromBytes(Bytes.fromHexString('0x1234')), ], [ ethereum.Value.fromSignedBigInt(BigInt.fromString('2')), - ethereum.Value.fromAddress(Address.fromString(dao)), - ethereum.Value.fromAddress(Address.fromString(plugin)), + ethereum.Value.fromAddress(daoAddress), + ethereum.Value.fromAddress(pluginAddress), ethereum.Value.fromAddress(Address.fromString(ADDRESS_SIX)), - ethereum.Value.fromBytes(Bytes.fromHexString('0x5678')) - ] + ethereum.Value.fromBytes(Bytes.fromHexString('0x5678')), + ], ]; - getSupportsInterface(plugin, TOKEN_VOTING_INTERFACE, false); - getSupportsInterface(plugin, ADDRESSLIST_VOTING_INTERFACE, false); - getSupportsInterface(plugin, ADMIN_INTERFACE, false); - getSupportsInterface(plugin, MULTISIG_INTERFACE, false); + getSupportsInterface(pluginEntityId, TOKEN_VOTING_INTERFACE_ID, false); + getSupportsInterface(pluginEntityId, ADDRESSLIST_VOTING_INTERFACE_ID, false); + getSupportsInterface(pluginEntityId, ADMIN_INTERFACE_ID, false); + getSupportsInterface(pluginEntityId, MULTISIG_INTERFACE_ID, false); let event = createInstallationPreparedEvent( ADDRESS_THREE, - dao, - plugin, + daoEntityId, + pluginEntityId, Bytes.fromHexString(setupId), - pluginSetupRepo, + pluginRepoEntityId, versionTuple, Bytes.fromHexString('0x00'), [ADDRESS_FOUR, ADDRESS_FIVE], @@ -98,64 +112,76 @@ test('InstallationPrepared event', function() { handleInstallationPrepared(event); assert.entityCount('PluginPreparation', 1); - assert.fieldEquals('PluginPreparation', preparationId, 'id', preparationId); assert.fieldEquals( 'PluginPreparation', - preparationId, + pluginPreparationEntityId, + 'id', + pluginPreparationEntityId + ); + assert.fieldEquals( + 'PluginPreparation', + pluginPreparationEntityId, 'installation', - installationIdString + pluginInstallationEntityId ); assert.fieldEquals( 'PluginPreparation', - preparationId, + pluginPreparationEntityId, 'creator', ADDRESS_THREE ); assert.fieldEquals( 'PluginPreparation', - preparationId, + pluginPreparationEntityId, 'dao', - dao.toLowerCase() + daoEntityId ); assert.fieldEquals( 'PluginPreparation', - preparationId, + pluginPreparationEntityId, 'preparedSetupId', setupId ); assert.fieldEquals( 'PluginPreparation', - preparationId, + pluginPreparationEntityId, 'pluginRepo', - pluginSetupRepo + pluginRepoEntityId ); assert.fieldEquals( 'PluginPreparation', - preparationId, + pluginPreparationEntityId, 'pluginVersion', pluginVersionId ); - assert.fieldEquals('PluginPreparation', preparationId, 'data', '0x00'); assert.fieldEquals( 'PluginPreparation', - preparationId, + pluginPreparationEntityId, + 'data', + '0x00' + ); + assert.fieldEquals( + 'PluginPreparation', + pluginPreparationEntityId, 'pluginAddress', - plugin.toLowerCase() + pluginEntityId ); assert.fieldEquals( 'PluginPreparation', - preparationId, + pluginPreparationEntityId, 'type', 'Installation' ); let helpers = [ Address.fromString(ADDRESS_FOUR), - Address.fromString(ADDRESS_FIVE) + Address.fromString(ADDRESS_FIVE), ]; - let pluginPreparation = PluginPreparation.load(preparationId); + let pluginPreparation = PluginPreparation.load(pluginPreparationEntityId); if (!pluginPreparation) { - throw new Error(`PluginPrepation with id ${preparationId} not found`); + throw new Error( + `PluginPrepation with id ${pluginPreparationEntityId} not found` + ); } assert.equals( @@ -166,45 +192,47 @@ test('InstallationPrepared event', function() { assert.entityCount('PluginPermission', permissions.length); for (let i = 0; i < permissions.length; i++) { let permission = permissions[i]; - let operation = PERMISSION_OPERATIONS.get(permission[0].toI32()); - let permissionEntityId = `${preparationId}_${operation}_${permission[1] - .toAddress() - .toHexString()}_${permission[2] - .toAddress() - .toHexString()}_${permission[4].toBytes().toHexString()}`; + let operation = permission[0].toI32(); + let pluginPermissionEntityId = generatePluginPermissionEntityId( + pluginPreparationEntityId, + operation, + permission[1].toAddress(), + permission[2].toAddress(), + permission[4].toBytes() + ); assert.fieldEquals( 'PluginPermission', - permissionEntityId, + pluginPermissionEntityId, 'id', - permissionEntityId + pluginPermissionEntityId ); assert.fieldEquals( 'PluginPermission', - permissionEntityId, + pluginPermissionEntityId, 'operation', - operation || '' + PERMISSION_OPERATIONS.get(operation) || '' ); assert.fieldEquals( 'PluginPermission', - permissionEntityId, + pluginPermissionEntityId, 'where', permission[1].toAddress().toHexString() ); assert.fieldEquals( 'PluginPermission', - permissionEntityId, + pluginPermissionEntityId, 'who', permission[2].toAddress().toHexString() ); assert.fieldEquals( 'PluginPermission', - permissionEntityId, + pluginPermissionEntityId, 'permissionId', permission[4].toBytes().toHexString() ); assert.fieldEquals( 'PluginPermission', - permissionEntityId, + pluginPermissionEntityId, 'condition', permission[3].toAddress().toHexString() ); @@ -213,13 +241,13 @@ test('InstallationPrepared event', function() { assert.entityCount('PluginInstallation', 1); assert.fieldEquals( 'PluginInstallation', - installationIdString, + pluginInstallationEntityId, 'dao', - dao.toLowerCase() + daoEntityId ); assert.fieldEquals( 'PluginInstallation', - installationIdString, + pluginInstallationEntityId, 'state', 'InstallationPrepared' ); @@ -232,20 +260,23 @@ test('InstallationPrepared event', function() { clearStore(); }); -test('InstallationApplied event', function() { - let dao = DAO_ADDRESS; - let plugin = ADDRESS_ONE; - let setupId = PLUGIN_SETUP_ID; - let installationId = getPluginInstallationId(dao, plugin); - if (!installationId) { +test('InstallationApplied event', function () { + let pluginInstallationEntityId = generatePluginInstallationEntityId( + daoAddress, + pluginAddress + ); + if (!pluginInstallationEntityId) { throw new Error('Failed to get installationId'); } - let installationIdString = installationId.toHexString(); - let preparationId = `${installationIdString}_${setupId}`; + pluginInstallationEntityId = pluginInstallationEntityId as string; + let pluginPreparationEntityId = generatePluginPreparationEntityId( + pluginInstallationEntityId, + Bytes.fromHexString(setupId) + ); let event = createInstallationAppliedEvent( - dao, - plugin, + daoEntityId, + pluginEntityId, Bytes.fromHexString(setupId), Bytes.fromHexString(APPLIED_PLUGIN_SETUP_ID) ); @@ -254,37 +285,37 @@ test('InstallationApplied event', function() { assert.entityCount('PluginInstallation', 1); assert.fieldEquals( 'PluginInstallation', - installationIdString, + pluginInstallationEntityId, 'id', - installationIdString + pluginInstallationEntityId ); assert.fieldEquals( 'PluginInstallation', - installationIdString, + pluginInstallationEntityId, 'dao', - dao.toLowerCase() + daoEntityId ); assert.fieldEquals( 'PluginInstallation', - installationIdString, + pluginInstallationEntityId, 'plugin', - plugin.toLowerCase() + pluginEntityId ); assert.fieldEquals( 'PluginInstallation', - installationIdString, + pluginInstallationEntityId, 'appliedPreparation', - preparationId + pluginPreparationEntityId ); assert.fieldEquals( 'PluginInstallation', - installationIdString, + pluginInstallationEntityId, 'appliedSetupId', APPLIED_PLUGIN_SETUP_ID ); assert.fieldEquals( 'PluginInstallation', - installationIdString, + pluginInstallationEntityId, 'state', 'Installed' ); @@ -292,47 +323,55 @@ test('InstallationApplied event', function() { clearStore(); }); -test('UpdatePrepared event', function() { - let dao = DAO_ADDRESS; - let plugin = ADDRESS_ONE; - let setupId = PLUGIN_SETUP_ID; - let pluginSetupRepo = ADDRESS_TWO; - let pluginVersionId = `${pluginSetupRepo}_1_2`; - let installationId = getPluginInstallationId(dao, plugin); - if (!installationId) { +test('UpdatePrepared event', function () { + const build = 1; + const release = 2; + const pluginVersionEntityId = generatePluginVersionEntityId( + pluginRepoAddress, + build, + release + ); + let pluginInstallationEntityId = generatePluginInstallationEntityId( + daoAddress, + pluginAddress + ); + if (!pluginInstallationEntityId) { throw new Error('Failed to get installationId'); } - let installationIdString = installationId.toHexString(); - let preparationId = `${installationIdString}_${setupId}`; + pluginInstallationEntityId = pluginInstallationEntityId as string; + const pluginPreparationEntityId = generatePluginPreparationEntityId( + pluginInstallationEntityId, + Bytes.fromHexString(setupId) + ); let versionTuple = new ethereum.Tuple(); - versionTuple.push(ethereum.Value.fromSignedBigInt(BigInt.fromString('1'))); - versionTuple.push(ethereum.Value.fromSignedBigInt(BigInt.fromString('2'))); + versionTuple.push(ethereum.Value.fromSignedBigInt(BigInt.fromI32(build))); + versionTuple.push(ethereum.Value.fromSignedBigInt(BigInt.fromI32(release))); let permissions = [ [ ethereum.Value.fromSignedBigInt(BigInt.fromString('0')), - ethereum.Value.fromAddress(Address.fromString(dao)), - ethereum.Value.fromAddress(Address.fromString(plugin)), + ethereum.Value.fromAddress(daoAddress), + ethereum.Value.fromAddress(pluginAddress), ethereum.Value.fromAddress(Address.fromString(ADDRESS_ZERO)), - ethereum.Value.fromBytes(Bytes.fromHexString('0x1234')) + ethereum.Value.fromBytes(Bytes.fromHexString('0x1234')), ], [ ethereum.Value.fromSignedBigInt(BigInt.fromString('2')), - ethereum.Value.fromAddress(Address.fromString(dao)), - ethereum.Value.fromAddress(Address.fromString(plugin)), + ethereum.Value.fromAddress(daoAddress), + ethereum.Value.fromAddress(pluginAddress), ethereum.Value.fromAddress(Address.fromString(ADDRESS_SIX)), - ethereum.Value.fromBytes(Bytes.fromHexString('0x5678')) - ] + ethereum.Value.fromBytes(Bytes.fromHexString('0x5678')), + ], ]; let event = createUpdatePreparedEvent( ADDRESS_THREE, - dao, - plugin, + daoEntityId, + pluginEntityId, Bytes.fromHexString(setupId), - pluginSetupRepo, + pluginRepoEntityId, versionTuple, [], [ADDRESS_FOUR, ADDRESS_FIVE], @@ -343,53 +382,70 @@ test('UpdatePrepared event', function() { handleUpdatePrepared(event); assert.entityCount('PluginPreparation', 1); - assert.fieldEquals('PluginPreparation', preparationId, 'id', preparationId); assert.fieldEquals( 'PluginPreparation', - preparationId, + pluginPreparationEntityId, + 'id', + pluginPreparationEntityId + ); + assert.fieldEquals( + 'PluginPreparation', + pluginPreparationEntityId, 'installation', - installationIdString + pluginInstallationEntityId ); assert.fieldEquals( 'PluginPreparation', - preparationId, + pluginPreparationEntityId, 'creator', ADDRESS_THREE ); assert.fieldEquals( 'PluginPreparation', - preparationId, + pluginPreparationEntityId, 'dao', - dao.toLowerCase() + daoEntityId ); assert.fieldEquals( 'PluginPreparation', - preparationId, + pluginPreparationEntityId, 'preparedSetupId', setupId ); assert.fieldEquals( 'PluginPreparation', - preparationId, + pluginPreparationEntityId, 'pluginRepo', - pluginSetupRepo + pluginRepoEntityId ); assert.fieldEquals( 'PluginPreparation', - preparationId, + pluginPreparationEntityId, 'pluginVersion', - pluginVersionId + pluginVersionEntityId + ); + assert.fieldEquals( + 'PluginPreparation', + pluginPreparationEntityId, + 'data', + '0x12' + ); + assert.fieldEquals( + 'PluginPreparation', + pluginPreparationEntityId, + 'type', + 'Update' ); - assert.fieldEquals('PluginPreparation', preparationId, 'data', '0x12'); - assert.fieldEquals('PluginPreparation', preparationId, 'type', 'Update'); let helpers = [ Address.fromString(ADDRESS_FOUR), - Address.fromString(ADDRESS_FIVE) + Address.fromString(ADDRESS_FIVE), ]; - let pluginPreparation = PluginPreparation.load(preparationId); + let pluginPreparation = PluginPreparation.load(pluginPreparationEntityId); if (!pluginPreparation) { - throw new Error(`PluginPrepation with id ${preparationId} not found`); + throw new Error( + `PluginPrepation with id ${pluginPreparationEntityId} not found` + ); } assert.equals( @@ -401,45 +457,47 @@ test('UpdatePrepared event', function() { for (let i = 0; i < permissions.length; i++) { let permission = permissions[i]; - let operation = PERMISSION_OPERATIONS.get(permission[0].toI32()); - let permissionEntityId = `${preparationId}_${operation}_${permission[1] - .toAddress() - .toHexString()}_${permission[2] - .toAddress() - .toHexString()}_${permission[4].toBytes().toHexString()}`; + let operation = permission[0].toI32(); + const pluginPermissionEntityId = generatePluginPermissionEntityId( + pluginPreparationEntityId, + operation, + permission[1].toAddress(), + permission[2].toAddress(), + permission[4].toBytes() + ); assert.fieldEquals( 'PluginPermission', - permissionEntityId, + pluginPermissionEntityId, 'id', - permissionEntityId + pluginPermissionEntityId ); assert.fieldEquals( 'PluginPermission', - permissionEntityId, + pluginPermissionEntityId, 'operation', - operation || '' + PERMISSION_OPERATIONS.get(operation) || '' ); assert.fieldEquals( 'PluginPermission', - permissionEntityId, + pluginPermissionEntityId, 'where', permission[1].toAddress().toHexString() ); assert.fieldEquals( 'PluginPermission', - permissionEntityId, + pluginPermissionEntityId, 'who', permission[2].toAddress().toHexString() ); assert.fieldEquals( 'PluginPermission', - permissionEntityId, + pluginPermissionEntityId, 'permissionId', permission[4].toBytes().toHexString() ); assert.fieldEquals( 'PluginPermission', - permissionEntityId, + pluginPermissionEntityId, 'condition', permission[3].toAddress().toHexString() ); @@ -448,13 +506,13 @@ test('UpdatePrepared event', function() { assert.entityCount('PluginInstallation', 1); assert.fieldEquals( 'PluginInstallation', - installationIdString, + pluginInstallationEntityId, 'dao', - dao.toLowerCase() + daoEntityId ); assert.fieldEquals( 'PluginInstallation', - installationIdString, + pluginInstallationEntityId, 'state', 'UpdatePrepared' ); @@ -462,25 +520,28 @@ test('UpdatePrepared event', function() { clearStore(); }); -test('UpdateApplied event', function() { - let dao = DAO_ADDRESS; - let plugin = ADDRESS_ONE; - let setupId = PLUGIN_SETUP_ID; - let installationId = getPluginInstallationId(dao, plugin); - if (!installationId) { +test('UpdateApplied event', function () { + let pluginInstallationEntityId = generatePluginInstallationEntityId( + daoAddress, + pluginAddress + ); + if (!pluginInstallationEntityId) { throw new Error('Failed to get installationId'); } - let installationIdString = installationId.toHexString(); - let preparationId = `${installationIdString}_${setupId}`; + pluginInstallationEntityId = pluginInstallationEntityId as string; + const pluginPreparationEntityId = generatePluginPreparationEntityId( + pluginInstallationEntityId, + Bytes.fromHexString(setupId) + ); - getSupportsInterface(plugin, TOKEN_VOTING_INTERFACE, false); - getSupportsInterface(plugin, ADDRESSLIST_VOTING_INTERFACE, false); - getSupportsInterface(plugin, ADMIN_INTERFACE, false); - getSupportsInterface(plugin, MULTISIG_INTERFACE, false); + getSupportsInterface(pluginEntityId, TOKEN_VOTING_INTERFACE_ID, false); + getSupportsInterface(pluginEntityId, ADDRESSLIST_VOTING_INTERFACE_ID, false); + getSupportsInterface(pluginEntityId, ADMIN_INTERFACE_ID, false); + getSupportsInterface(pluginEntityId, MULTISIG_INTERFACE_ID, false); let event = createUpdateAppliedEvent( - dao, - plugin, + daoEntityId, + pluginEntityId, Bytes.fromHexString(setupId), Bytes.fromHexString(APPLIED_PLUGIN_SETUP_ID) ); @@ -489,37 +550,37 @@ test('UpdateApplied event', function() { assert.entityCount('PluginInstallation', 1); assert.fieldEquals( 'PluginInstallation', - installationIdString, + pluginInstallationEntityId, 'id', - installationIdString + pluginInstallationEntityId ); assert.fieldEquals( 'PluginInstallation', - installationIdString, + pluginInstallationEntityId, 'dao', - dao.toLowerCase() + daoEntityId ); assert.fieldEquals( 'PluginInstallation', - installationIdString, + pluginInstallationEntityId, 'plugin', - plugin.toLowerCase() + pluginEntityId ); assert.fieldEquals( 'PluginInstallation', - installationIdString, + pluginInstallationEntityId, 'appliedPreparation', - preparationId + pluginPreparationEntityId ); assert.fieldEquals( 'PluginInstallation', - installationIdString, + pluginInstallationEntityId, 'appliedSetupId', APPLIED_PLUGIN_SETUP_ID ); assert.fieldEquals( 'PluginInstallation', - installationIdString, + pluginInstallationEntityId, 'state', 'Installed' ); @@ -527,48 +588,55 @@ test('UpdateApplied event', function() { clearStore(); }); -test('UninstallationPrepared event', function() { - let dao = DAO_ADDRESS; - let plugin = ADDRESS_ONE; - let setupId = PLUGIN_SETUP_ID; - let pluginSetupRepo = ADDRESS_TWO; - let installationId = getPluginInstallationId(dao, plugin); - if (!installationId) { +test('UninstallationPrepared event', function () { + let pluginInstallationEntityId = generatePluginInstallationEntityId( + daoAddress, + pluginAddress + ); + if (!pluginInstallationEntityId) { throw new Error('Failed to get installationId'); } - let installationIdString = installationId.toHexString(); - let preparationId = `${installationIdString}_${setupId}`; - - let pluginVersionId = `${pluginSetupRepo}_1_2`; + pluginInstallationEntityId = pluginInstallationEntityId as string; + const build = 1; + const release = 2; + const pluginPreparationEntityId = generatePluginPreparationEntityId( + pluginInstallationEntityId, + Bytes.fromHexString(setupId) + ); + const pluginVersionEntityId = generatePluginVersionEntityId( + pluginRepoAddress, + build, + release + ); let versionTuple = new ethereum.Tuple(); - versionTuple.push(ethereum.Value.fromSignedBigInt(BigInt.fromString('1'))); - versionTuple.push(ethereum.Value.fromSignedBigInt(BigInt.fromString('2'))); + versionTuple.push(ethereum.Value.fromSignedBigInt(BigInt.fromI32(build))); + versionTuple.push(ethereum.Value.fromSignedBigInt(BigInt.fromI32(release))); let permissions = [ [ ethereum.Value.fromSignedBigInt(BigInt.fromString('0')), - ethereum.Value.fromAddress(Address.fromString(dao)), - ethereum.Value.fromAddress(Address.fromString(plugin)), + ethereum.Value.fromAddress(daoAddress), + ethereum.Value.fromAddress(pluginAddress), ethereum.Value.fromAddress(Address.fromString(ADDRESS_ZERO)), - ethereum.Value.fromBytes(Bytes.fromHexString('0x1234')) + ethereum.Value.fromBytes(Bytes.fromHexString('0x1234')), ], [ ethereum.Value.fromSignedBigInt(BigInt.fromString('2')), - ethereum.Value.fromAddress(Address.fromString(dao)), - ethereum.Value.fromAddress(Address.fromString(plugin)), + ethereum.Value.fromAddress(daoAddress), + ethereum.Value.fromAddress(pluginAddress), ethereum.Value.fromAddress(Address.fromString(ADDRESS_SIX)), - ethereum.Value.fromBytes(Bytes.fromHexString('0x5678')) - ] + ethereum.Value.fromBytes(Bytes.fromHexString('0x5678')), + ], ]; let event = createUninstallationPreparedEvent( ADDRESS_THREE, - dao, + daoEntityId, Bytes.fromHexString(setupId), - pluginSetupRepo, + pluginRepoEntityId, versionTuple, - plugin, + pluginEntityId, [ADDRESS_FOUR, ADDRESS_FIVE], Bytes.fromHexString('0x00'), permissions @@ -576,53 +644,60 @@ test('UninstallationPrepared event', function() { handleUninstallationPrepared(event); assert.entityCount('PluginPreparation', 1); - assert.fieldEquals('PluginPreparation', preparationId, 'id', preparationId); assert.fieldEquals( 'PluginPreparation', - preparationId, + pluginPreparationEntityId, + 'id', + pluginPreparationEntityId + ); + assert.fieldEquals( + 'PluginPreparation', + pluginPreparationEntityId, 'installation', - installationIdString + pluginInstallationEntityId ); assert.fieldEquals( 'PluginPreparation', - preparationId, + pluginPreparationEntityId, 'creator', ADDRESS_THREE ); assert.fieldEquals( 'PluginPreparation', - preparationId, + pluginPreparationEntityId, 'dao', - dao.toLowerCase() + daoEntityId ); assert.fieldEquals( 'PluginPreparation', - preparationId, + pluginPreparationEntityId, 'preparedSetupId', setupId ); assert.fieldEquals( 'PluginPreparation', - preparationId, + pluginPreparationEntityId, 'pluginRepo', - pluginSetupRepo + pluginRepoEntityId ); assert.fieldEquals( 'PluginPreparation', - preparationId, + pluginPreparationEntityId, 'pluginVersion', - pluginVersionId + pluginVersionEntityId ); assert.fieldEquals( 'PluginPreparation', - preparationId, + pluginPreparationEntityId, 'type', 'Uninstallation' ); - let pluginPreparation = PluginPreparation.load(preparationId); + let pluginPreparation = PluginPreparation.load(pluginPreparationEntityId); if (!pluginPreparation) { - throw new Error(`PluginPrepation with id ${preparationId} not found`); + throw new Error( + `PluginPrepation with id ${pluginPreparationEntityId} not found` + ); } assert.equals( ethereum.Value.fromBytesArray(pluginPreparation.helpers), @@ -632,46 +707,48 @@ test('UninstallationPrepared event', function() { assert.entityCount('PluginPermission', 2); for (let i = 0; i < permissions.length; i++) { - let permission = permissions[i]; - let operation = PERMISSION_OPERATIONS.get(permission[0].toI32()); - let permissionEntityId = `${preparationId}_${operation}_${permission[1] - .toAddress() - .toHexString()}_${permission[2] - .toAddress() - .toHexString()}_${permission[4].toBytes().toHexString()}`; + const permission = permissions[i]; + const operation = permission[0].toI32(); + const pluginPermissionEntityId = generatePluginPermissionEntityId( + pluginPreparationEntityId, + operation, + permission[1].toAddress(), + permission[2].toAddress(), + permission[4].toBytes() + ); assert.fieldEquals( 'PluginPermission', - permissionEntityId, + pluginPermissionEntityId, 'id', - permissionEntityId + pluginPermissionEntityId ); assert.fieldEquals( 'PluginPermission', - permissionEntityId, + pluginPermissionEntityId, 'operation', - operation || '' + PERMISSION_OPERATIONS.get(operation) || '' ); assert.fieldEquals( 'PluginPermission', - permissionEntityId, + pluginPermissionEntityId, 'where', permission[1].toAddress().toHexString() ); assert.fieldEquals( 'PluginPermission', - permissionEntityId, + pluginPermissionEntityId, 'who', permission[2].toAddress().toHexString() ); assert.fieldEquals( 'PluginPermission', - permissionEntityId, + pluginPermissionEntityId, 'permissionId', permission[4].toBytes().toHexString() ); assert.fieldEquals( 'PluginPermission', - permissionEntityId, + pluginPermissionEntityId, 'condition', permission[3].toAddress().toHexString() ); @@ -680,13 +757,13 @@ test('UninstallationPrepared event', function() { assert.entityCount('PluginInstallation', 1); assert.fieldEquals( 'PluginInstallation', - installationIdString, + pluginInstallationEntityId, 'dao', - dao.toLowerCase() + daoEntityId ); assert.fieldEquals( 'PluginInstallation', - installationIdString, + pluginInstallationEntityId, 'state', 'UninstallPrepared' ); @@ -694,36 +771,43 @@ test('UninstallationPrepared event', function() { clearStore(); }); -test('UninstallationApplied event', function() { - let dao = DAO_ADDRESS; - let plugin = ADDRESS_ONE; - let setupId = PLUGIN_SETUP_ID; - let installationId = getPluginInstallationId(dao, plugin); - if (!installationId) { - throw new Error('Failed to get installationId'); +test('UninstallationApplied event', function () { + let pluginInstallationEntityId = generatePluginInstallationEntityId( + daoAddress, + pluginAddress + ); + if (!pluginInstallationEntityId) { + throw new Error('Failed to get pluginInstallationEntityId'); } - let installationIdString = installationId.toHexString(); - let preparationId = `${installationIdString}_${setupId}`; + pluginInstallationEntityId = pluginInstallationEntityId as string; + const pluginPreparationEntityId = generatePluginPreparationEntityId( + pluginInstallationEntityId, + Bytes.fromHexString(setupId) + ); - let event = createUninstallationAppliedEvent(dao, plugin, setupId); + let event = createUninstallationAppliedEvent( + daoEntityId, + pluginEntityId, + setupId + ); handleUninstallationApplied(event); assert.entityCount('PluginInstallation', 1); assert.fieldEquals( 'PluginInstallation', - installationIdString, + pluginInstallationEntityId, 'dao', - dao.toLowerCase() + daoEntityId ); assert.fieldEquals( 'PluginInstallation', - installationIdString, + pluginInstallationEntityId, 'appliedPreparation', - preparationId + pluginPreparationEntityId ); assert.fieldEquals( 'PluginInstallation', - installationIdString, + pluginInstallationEntityId, 'state', 'Uninstalled' ); diff --git a/packages/subgraph/tests/plugin/utils.ts b/packages/subgraph/tests/plugin/utils.ts index d190e021a..7a82acd0f 100644 --- a/packages/subgraph/tests/plugin/utils.ts +++ b/packages/subgraph/tests/plugin/utils.ts @@ -1,9 +1,3 @@ -import {Address, BigInt, Bytes, ethereum} from '@graphprotocol/graph-ts'; -import {newMockEvent} from 'matchstick-as'; -import { - ReleaseMetadataUpdated, - VersionCreated -} from '../../generated/templates/PluginRepoTemplate/PluginRepo'; import { InstallationApplied, InstallationPrepared, @@ -13,8 +7,14 @@ import { UpdateApplied, UpdatePrepared, UpdatePreparedPreparedSetupDataStruct, - UpdatePreparedSetupPayloadStruct + UpdatePreparedSetupPayloadStruct, } from '../../generated/PluginSetupProcessor/PluginSetupProcessor'; +import { + ReleaseMetadataUpdated, + VersionCreated, +} from '../../generated/templates/PluginRepoTemplate/PluginRepo'; +import {Address, BigInt, Bytes, ethereum} from '@graphprotocol/graph-ts'; +import {newMockEvent} from 'matchstick-as'; export function createReleaseMetadataUpdatedEvent( release: string, diff --git a/packages/subgraph/tests/registry/daoRegistry.test.ts b/packages/subgraph/tests/registry/daoRegistry.test.ts index b72f569dd..85f279db6 100644 --- a/packages/subgraph/tests/registry/daoRegistry.test.ts +++ b/packages/subgraph/tests/registry/daoRegistry.test.ts @@ -1,17 +1,17 @@ +import {Dao} from '../../generated/schema'; +import {handleDAORegistered} from '../../src/registries/daoRegistry'; +import {DAO_ADDRESS, ADDRESS_ONE} from '../constants'; +import {createNewDaoEvent} from './utils'; +import {generateDaoEntityId} from '@aragon/osx-commons-subgraph'; +import {Address} from '@graphprotocol/graph-ts'; import { assert, clearStore, test, dataSourceMock, describe, - afterEach + afterEach, } from 'matchstick-as/assembly/index'; -import {Address} from '@graphprotocol/graph-ts'; - -import {handleDAORegistered} from '../../src/registries/daoRegistry'; -import {DAO_ADDRESS, ADDRESS_ONE} from '../constants'; -import {createNewDaoEvent} from './utils'; -import {Dao} from '../../generated/schema'; describe('DAORegistry', () => { afterEach(() => { @@ -25,20 +25,21 @@ describe('DAORegistry', () => { // handle event handleDAORegistered(newDaoEvent); - let entityID = Address.fromString(DAO_ADDRESS).toHexString(); + const daoAddress = Address.fromString(DAO_ADDRESS); + const daoEntityId = generateDaoEntityId(daoAddress); // checks - assert.fieldEquals('Dao', entityID, 'id', entityID); + assert.fieldEquals('Dao', daoEntityId, 'id', daoEntityId); assert.fieldEquals( 'Dao', - entityID, + daoEntityId, 'creator', Address.fromString(ADDRESS_ONE).toHexString() ); - assert.fieldEquals('Dao', entityID, 'subdomain', 'mock-Dao'); + assert.fieldEquals('Dao', daoEntityId, 'subdomain', 'mock-Dao'); assert.fieldEquals( 'Dao', - entityID, + daoEntityId, 'createdAt', newDaoEvent.block.timestamp.toString() ); @@ -46,29 +47,33 @@ describe('DAORegistry', () => { test("Don't store subdomain for blocklisted DAO", () => { // Using an already blocklisted address for mainnet. This is unlikely to change - let entityID = '0x16070493aa513f91fc8957f14b7b7c6c0c41fbac'; + let denylistedEntityId = '0x16070493aa513f91fc8957f14b7b7c6c0c41fbac'; // create event. - let newDaoEvent = createNewDaoEvent(entityID, ADDRESS_ONE, 'mock-Dao'); + let newDaoEvent = createNewDaoEvent( + denylistedEntityId, + ADDRESS_ONE, + 'mock-Dao' + ); dataSourceMock.setNetwork('mainnet'); handleDAORegistered(newDaoEvent); // checks - assert.fieldEquals('Dao', entityID, 'id', entityID); + assert.fieldEquals('Dao', denylistedEntityId, 'id', denylistedEntityId); assert.fieldEquals( 'Dao', - entityID, + denylistedEntityId, 'creator', Address.fromString(ADDRESS_ONE).toHexString() ); assert.fieldEquals( 'Dao', - entityID, + denylistedEntityId, 'createdAt', newDaoEvent.block.timestamp.toString() ); - const daoEntity = Dao.load(entityID); + const daoEntity = Dao.load(denylistedEntityId); assert.assertNull(daoEntity!.subdomain); }); }); diff --git a/packages/subgraph/tests/registry/pluginRepoRegistry.test.ts b/packages/subgraph/tests/registry/pluginRepoRegistry.test.ts index add7c3f89..51f65b20e 100644 --- a/packages/subgraph/tests/registry/pluginRepoRegistry.test.ts +++ b/packages/subgraph/tests/registry/pluginRepoRegistry.test.ts @@ -1,19 +1,26 @@ -import {assert, clearStore, test} from 'matchstick-as/assembly/index'; +import {handlePluginRepoRegistered} from '../../src/registries/pluginRepoRegistry'; import {ADDRESS_ONE} from '../constants'; import {createPluginRepoRegisteredEvent} from './utils'; -import {handlePluginRepoRegistered} from '../../src/registries/pluginRepoRegistry'; +import {generatePluginRepoEntityId} from '@aragon/osx-commons-subgraph'; import {Address} from '@graphprotocol/graph-ts'; +import {assert, clearStore, test} from 'matchstick-as/assembly/index'; test('Run plugin repo registry mappings with mock event', () => { - let id = Address.fromString(ADDRESS_ONE).toHexString(); + const pluginRepoAddress = Address.fromString(ADDRESS_ONE); + const pluginRepoEntityId = generatePluginRepoEntityId(pluginRepoAddress); let newRepoRegisteredEvent = createPluginRepoRegisteredEvent( 'plugin-repo', - ADDRESS_ONE + pluginRepoEntityId ); handlePluginRepoRegistered(newRepoRegisteredEvent); - assert.fieldEquals('PluginRepo', id, 'subdomain', 'plugin-repo'); + assert.fieldEquals( + 'PluginRepo', + pluginRepoEntityId, + 'subdomain', + 'plugin-repo' + ); clearStore(); }); diff --git a/packages/subgraph/tests/registry/utils.ts b/packages/subgraph/tests/registry/utils.ts index 26baeed91..e9c5837cb 100644 --- a/packages/subgraph/tests/registry/utils.ts +++ b/packages/subgraph/tests/registry/utils.ts @@ -1,8 +1,7 @@ -import {Address, ethereum} from '@graphprotocol/graph-ts'; -import {newMockEvent} from 'matchstick-as/assembly/index'; - import {DAORegistered} from '../../generated/DAORegistry/DAORegistry'; import {PluginRepoRegistered} from '../../generated/PluginRepoRegistry/PluginRepoRegistry'; +import {Address, ethereum} from '@graphprotocol/graph-ts'; +import {newMockEvent} from 'matchstick-as/assembly/index'; // events diff --git a/packages/subgraph/tests/schema-extender.ts b/packages/subgraph/tests/schema-extender.ts index 76e351796..149915fba 100644 --- a/packages/subgraph/tests/schema-extender.ts +++ b/packages/subgraph/tests/schema-extender.ts @@ -11,7 +11,7 @@ function main() { './tests/helpers/extended-schema.ts', '', { - overwrite: true + overwrite: true, } ); @@ -34,16 +34,17 @@ function main() { // Import assert into generated file outputFile.addImportDeclaration({ namedImports: ['assert', 'log'], - moduleSpecifier: `matchstick-as` + moduleSpecifier: `matchstick-as`, }); // Add import statements for the original classes - const sourceFileNameWithoutExtension = sourceFile.getBaseNameWithoutExtension(); + const sourceFileNameWithoutExtension = + sourceFile.getBaseNameWithoutExtension(); outputFile.addImportDeclaration({ namedImports: sourceClasses.map( classDeclaration => classDeclaration.getName() as string ), - moduleSpecifier: `../../generated/${sourceFileNameWithoutExtension}` + moduleSpecifier: `../../generated/${sourceFileNameWithoutExtension}`, }); // Iterate through the classes in the source file @@ -54,7 +55,7 @@ function main() { const newClass = outputFile.addClass({ name: newClassName, isExported: true, - extends: originalClassName + extends: originalClassName, }); // Create a new constructor that calls super() with a default id. @@ -63,7 +64,7 @@ function main() { statements: (writer: CodeBlockWriter) => { const defaultEntityId = '0x1'; writer.writeLine(`super('${defaultEntityId}');`); - } + }, }); // add methods to generated classes @@ -77,9 +78,15 @@ function main() { returnType = `Extended${originalClassName}`; } + // Get the type parameters + const typeParameters = method + .getTypeParameters() + .map(param => param.getText()); + const newMethod = newClass.addMethod({ name: method.getName(), - returnType: returnType + returnType: returnType, + typeParameters: typeParameters, // Add the type parameters to the new method }); const parameters = method.getParameters().map(parameter => { @@ -90,7 +97,7 @@ function main() { initializer: parameter.getInitializer()?.getText(), decorators: parameter .getDecorators() - .map(decorator => decorator.getStructure()) + .map(decorator => decorator.getStructure()), }; }); @@ -109,7 +116,7 @@ function main() { returnType: 'void', statements: (writer: CodeBlockWriter) => { writer.writeLine('this.save();'); - } + }, }); newClass.addMethod({ @@ -119,8 +126,8 @@ function main() { { name: 'debug', type: 'boolean', - initializer: 'false' - } + initializer: 'false', + }, ], statements: (writer: CodeBlockWriter) => { writer.writeLine(`let entity = ${originalClassName}.load(this.id);`); @@ -157,7 +164,7 @@ function main() { ); }); }); - } + }, }); }); diff --git a/packages/subgraph/tests/token/governance-erc20.test.ts b/packages/subgraph/tests/token/governance-erc20.test.ts index 38e8aabed..750273221 100644 --- a/packages/subgraph/tests/token/governance-erc20.test.ts +++ b/packages/subgraph/tests/token/governance-erc20.test.ts @@ -1,33 +1,67 @@ import { - assert, - afterEach, - beforeAll, - clearStore, - dataSourceMock, - test, - describe -} from 'matchstick-as'; + handleDelegateChanged, + handleDelegateVotesChanged, + handleTransfer, +} from '../../src/packages/token/governance-erc20'; +import {generateMemberEntityId} from '../../src/utils/ids'; import { ADDRESS_ONE, ADDRESS_SIX, ADDRESS_TWO, ONE_ETH, - ADDRESS_THREE + ADDRESS_THREE, + DAO_TOKEN_ADDRESS, + ADDRESS_SEVEN, } from '../constants'; -import {createNewERC20TransferEvent, createTokenVotingMember} from './utils'; -import { - handleDelegateChanged, - handleDelegateVotesChanged, - handleTransfer -} from '../../src/packages/token/governance-erc20'; -import {BigInt, DataSourceContext} from '@graphprotocol/graph-ts'; +import {getBalanceOf} from '../dao/utils'; import {ExtendedTokenVotingMember} from '../helpers/extended-schema'; +import { + createNewDelegateChangedEvent, + createNewERC20TransferEvent, + createNewERC20TransferEventWithAddress, + createTokenVotingMember, + getDelegatee, + getVotes, +} from './utils'; +import { + generateEntityIdFromAddress, + generatePluginEntityId, +} from '@aragon/osx-commons-subgraph'; +import {Address, BigInt, DataSourceContext, log} from '@graphprotocol/graph-ts'; +import { + assert, + afterEach, + beforeAll, + clearStore, + dataSourceMock, + test, + describe, +} from 'matchstick-as'; + +// mock plugins +const pluginAddress = Address.fromString(ADDRESS_SIX); +const pluginEntityId = generatePluginEntityId(pluginAddress); +const pluginAddressSecond = Address.fromString(ADDRESS_SEVEN); +const pluginEntityIdSecond = generatePluginEntityId(pluginAddressSecond); + +// mock members +const fromAddress = Address.fromString(ADDRESS_ONE); +const memberAddress = fromAddress; +const toAddress = Address.fromString(ADDRESS_TWO); +const fromAddressHexString = fromAddress.toHexString(); +const memberAddressHexString = fromAddressHexString; +const toAddressHexString = toAddress.toHexString(); +const thirdAddress = Address.fromString(ADDRESS_THREE); + +function setContext(pluginId: string): void { + const context = new DataSourceContext(); + context.setString('pluginId', pluginId); + dataSourceMock.setContext(context); +} describe('Governance ERC20', () => { beforeAll(() => { - const context = new DataSourceContext(); - context.setString('pluginId', ADDRESS_SIX); - dataSourceMock.setContext(context); + setContext(pluginEntityId); }); afterEach(() => { @@ -36,235 +70,431 @@ describe('Governance ERC20', () => { describe('handleTransfer', () => { test('it should create a new member of from', () => { - const mockEvent = createNewERC20TransferEvent( - ADDRESS_ONE, - ADDRESS_TWO, - ONE_ETH + const mockEvent = createNewERC20TransferEventWithAddress( + fromAddressHexString, + toAddressHexString, + ONE_ETH, + DAO_TOKEN_ADDRESS ); + getBalanceOf(DAO_TOKEN_ADDRESS, fromAddress.toHexString(), '0'); + getBalanceOf(DAO_TOKEN_ADDRESS, toAddress.toHexString(), ONE_ETH); + getDelegatee(DAO_TOKEN_ADDRESS, fromAddress.toHexString(), null); + getDelegatee(DAO_TOKEN_ADDRESS, toAddress.toHexString(), null); + getVotes(DAO_TOKEN_ADDRESS, fromAddress.toHexString(), '0'); + getVotes(DAO_TOKEN_ADDRESS, toAddress.toHexString(), ONE_ETH); + handleTransfer(mockEvent); - const fromUserId = ADDRESS_ONE.concat('_').concat(ADDRESS_SIX); - assert.fieldEquals('TokenVotingMember', fromUserId, 'id', fromUserId); + const memberEntityId = generateMemberEntityId(pluginAddress, fromAddress); assert.fieldEquals( 'TokenVotingMember', - fromUserId, - 'address', - ADDRESS_ONE + memberEntityId, + 'id', + memberEntityId ); assert.fieldEquals( 'TokenVotingMember', - fromUserId, - 'plugin', - ADDRESS_SIX + memberEntityId, + 'address', + fromAddressHexString ); assert.fieldEquals( 'TokenVotingMember', - fromUserId, - 'balance', - `-${ONE_ETH}` + memberEntityId, + 'plugin', + pluginEntityId ); + assert.fieldEquals('TokenVotingMember', memberEntityId, 'balance', '0'); }); test('it should create a new member of to', () => { - const mockEvent = createNewERC20TransferEvent( - ADDRESS_ONE, - ADDRESS_TWO, - ONE_ETH + const mockEvent = createNewERC20TransferEventWithAddress( + fromAddressHexString, + toAddressHexString, + ONE_ETH, + DAO_TOKEN_ADDRESS ); + getBalanceOf(DAO_TOKEN_ADDRESS, fromAddress.toHexString(), '0'); + getBalanceOf(DAO_TOKEN_ADDRESS, toAddress.toHexString(), ONE_ETH); + handleTransfer(mockEvent); - const toUserId = ADDRESS_TWO.concat('_').concat(ADDRESS_SIX); - assert.fieldEquals('TokenVotingMember', toUserId, 'id', toUserId); - assert.fieldEquals('TokenVotingMember', toUserId, 'address', ADDRESS_TWO); - assert.fieldEquals('TokenVotingMember', toUserId, 'plugin', ADDRESS_SIX); - assert.fieldEquals('TokenVotingMember', toUserId, 'balance', ONE_ETH); + const memberEntityId = generateMemberEntityId(pluginAddress, toAddress); + assert.fieldEquals( + 'TokenVotingMember', + memberEntityId, + 'id', + memberEntityId + ); + assert.fieldEquals( + 'TokenVotingMember', + memberEntityId, + 'address', + toAddressHexString + ); + assert.fieldEquals( + 'TokenVotingMember', + memberEntityId, + 'plugin', + pluginEntityId + ); + assert.fieldEquals( + 'TokenVotingMember', + memberEntityId, + 'balance', + ONE_ETH + ); }); test('it should update an existing from entity', () => { - const fromUserId = createTokenVotingMember( - ADDRESS_ONE, - ADDRESS_SIX, - ONE_ETH + '0' + const memberEntityId = createTokenVotingMember( + fromAddressHexString, + pluginEntityId, + ONE_ETH + '0' /* 10 ETH */ ); - const mockEvent = createNewERC20TransferEvent( - ADDRESS_ONE, - ADDRESS_TWO, - ONE_ETH + const mockEvent = createNewERC20TransferEventWithAddress( + fromAddressHexString, + toAddressHexString, + ONE_ETH, + DAO_TOKEN_ADDRESS ); + getBalanceOf(DAO_TOKEN_ADDRESS, fromAddress.toHexString(), '0'); + getBalanceOf(DAO_TOKEN_ADDRESS, toAddress.toHexString(), ONE_ETH + '0'); + handleTransfer(mockEvent); - assert.fieldEquals('TokenVotingMember', fromUserId, 'id', fromUserId); assert.fieldEquals( 'TokenVotingMember', - fromUserId, + memberEntityId, + 'id', + memberEntityId + ); + assert.fieldEquals( + 'TokenVotingMember', + memberEntityId, 'address', - ADDRESS_ONE + fromAddressHexString ); assert.fieldEquals( 'TokenVotingMember', - fromUserId, + memberEntityId, 'plugin', - ADDRESS_SIX + pluginEntityId ); assert.fieldEquals( 'TokenVotingMember', - fromUserId, + memberEntityId, 'balance', - BigInt.fromString(ONE_ETH) - .times(BigInt.fromString('9')) - .toString() + BigInt.fromString(ONE_ETH).times(BigInt.fromString('9')).toString() ); }); test('it should update an existing to entity', () => { - const toUserId = createTokenVotingMember( - ADDRESS_TWO, - ADDRESS_SIX, + const memberEntityId = createTokenVotingMember( + toAddressHexString, + pluginEntityId, ONE_ETH + '0' ); - const mockEvent = createNewERC20TransferEvent( - ADDRESS_ONE, - ADDRESS_TWO, - ONE_ETH + const mockEvent = createNewERC20TransferEventWithAddress( + fromAddressHexString, + toAddressHexString, + ONE_ETH, + DAO_TOKEN_ADDRESS ); + getBalanceOf(DAO_TOKEN_ADDRESS, fromAddress.toHexString(), ONE_ETH + '0'); + getBalanceOf(DAO_TOKEN_ADDRESS, toAddress.toHexString(), '0'); handleTransfer(mockEvent); - assert.fieldEquals('TokenVotingMember', toUserId, 'id', toUserId); - assert.fieldEquals('TokenVotingMember', toUserId, 'address', ADDRESS_TWO); - assert.fieldEquals('TokenVotingMember', toUserId, 'plugin', ADDRESS_SIX); + + assert.fieldEquals( + 'TokenVotingMember', + memberEntityId, + 'id', + memberEntityId + ); + assert.fieldEquals( + 'TokenVotingMember', + memberEntityId, + 'address', + ADDRESS_TWO + ); assert.fieldEquals( 'TokenVotingMember', - toUserId, + memberEntityId, + 'plugin', + ADDRESS_SIX + ); + assert.fieldEquals( + 'TokenVotingMember', + memberEntityId, 'balance', - BigInt.fromString(ONE_ETH) - .times(BigInt.fromString('11')) - .toString() + BigInt.fromString(ONE_ETH).times(BigInt.fromString('11')).toString() ); }); + + test("it should initialize with the user's existing balance, if she has one", () => { + // constants + const STARTING_BALANCE = '10'; + const TRANSFER = '3'; + const REMAINING = '7'; + + // mocked calls + getBalanceOf(DAO_TOKEN_ADDRESS, fromAddress.toHexString(), REMAINING); + getBalanceOf(DAO_TOKEN_ADDRESS, toAddress.toHexString(), TRANSFER); + + const memberEntityIdFrom = createTokenVotingMember( + fromAddressHexString, + pluginEntityId, + STARTING_BALANCE + ); + + const memberEntityIdFromSecondPlugin = generateMemberEntityId( + pluginAddressSecond, + Address.fromString(fromAddressHexString) + ); + + const memberEntityIdTo = generateMemberEntityId( + pluginAddressSecond, + Address.fromString(toAddressHexString) + ); + + const memberEntityIdToSecondPlugin = generateMemberEntityId( + pluginAddressSecond, + Address.fromString(toAddressHexString) + ); + + // handle a transfer to another user + const transferEvent = createNewERC20TransferEventWithAddress( + fromAddressHexString, + toAddressHexString, + TRANSFER, + DAO_TOKEN_ADDRESS + ); + + assert.fieldEquals( + 'TokenVotingMember', + memberEntityIdFrom, + 'id', + memberEntityIdFrom + ); + assert.fieldEquals( + 'TokenVotingMember', + memberEntityIdFrom, + 'address', + fromAddressHexString + ); + assert.fieldEquals( + 'TokenVotingMember', + memberEntityIdFrom, + 'plugin', + pluginEntityId + ); + assert.fieldEquals( + 'TokenVotingMember', + memberEntityIdFrom, + 'balance', + STARTING_BALANCE + ); + + // execute the transfer in the context of both plugins + handleTransfer(transferEvent); + setContext(pluginEntityIdSecond); + handleTransfer(transferEvent); + + // we should see: + // user A has balances updated in both plugins + assert.fieldEquals( + 'TokenVotingMember', + memberEntityIdFrom, + 'balance', + REMAINING + ); + + assert.fieldEquals( + 'TokenVotingMember', + memberEntityIdFromSecondPlugin, + 'balance', + REMAINING + ); + + // user B has balances updated in both plugins + assert.fieldEquals( + 'TokenVotingMember', + memberEntityIdTo, + 'balance', + TRANSFER + ); + + assert.fieldEquals( + 'TokenVotingMember', + memberEntityIdToSecondPlugin, + 'balance', + TRANSFER + ); + + // set the context back to the first plugin + setContext(pluginEntityId); + }); }); describe('handleDelegateChanged', () => { + beforeAll(() => { + getBalanceOf(DAO_TOKEN_ADDRESS, fromAddress.toHexString(), '0'); + getBalanceOf(DAO_TOKEN_ADDRESS, toAddress.toHexString(), '0'); + getBalanceOf(DAO_TOKEN_ADDRESS, thirdAddress.toHexString(), '0'); + getVotes(DAO_TOKEN_ADDRESS, fromAddress.toHexString(), '0'); + getVotes(DAO_TOKEN_ADDRESS, toAddress.toHexString(), '0'); + getVotes(DAO_TOKEN_ADDRESS, thirdAddress.toHexString(), '0'); + getDelegatee(DAO_TOKEN_ADDRESS, fromAddress.toHexString(), null); + getDelegatee(DAO_TOKEN_ADDRESS, toAddress.toHexString(), null); + getDelegatee(DAO_TOKEN_ADDRESS, thirdAddress.toHexString(), null); + }); + test('it should create a member from `fromDelegate`.', () => { - let memberAddress = ADDRESS_ONE; - let pluginAddress = ADDRESS_SIX; let member = new ExtendedTokenVotingMember().withDefaultValues( - memberAddress, - pluginAddress + memberAddressHexString, + pluginEntityId ); let event = member.createEvent_DelegateChanged(); handleDelegateChanged(event); + member.delegatee = generateMemberEntityId(pluginAddress, memberAddress); member.assertEntity(); assert.entityCount('TokenVotingMember', 1); }); test('it should create a member from `toDelegate`.', () => { - let memberAddress = ADDRESS_ONE; - let pluginAddress = ADDRESS_SIX; + const memberTwoAddress = Address.fromString(ADDRESS_TWO); + const memberTwoAddressHexString = memberTwoAddress.toHexString(); let member = new ExtendedTokenVotingMember().withDefaultValues( - memberAddress, - pluginAddress + memberAddressHexString, + pluginEntityId ); let event = member.createEvent_DelegateChanged( - memberAddress, - ADDRESS_ONE, - ADDRESS_TWO + memberAddressHexString, + memberAddressHexString, + memberTwoAddressHexString ); handleDelegateChanged(event); - // assert - // expected changes - member.delegatee = ADDRESS_TWO.concat('_').concat(pluginAddress); + let expectedDelegatee = generateMemberEntityId( + pluginAddress, + memberTwoAddress + ); + + member.delegatee = expectedDelegatee; member.assertEntity(); + assert.entityCount('TokenVotingMember', 2); + assert.fieldEquals( + 'TokenVotingMember', + member.id, + 'delegatee', + expectedDelegatee + ); + assert.fieldEquals( + 'TokenVotingMember', + member.id, + 'address', + memberAddressHexString + ); + assert.fieldEquals( + 'TokenVotingMember', + member.id, + 'plugin', + pluginEntityId + ); + assert.fieldEquals('TokenVotingMember', member.id, 'balance', '0'); }); test('it should create a member for `delegator`, `fromDelegate` and `toDelegate`, and set delegatee as `toDelegate`.', () => { - let memberAddress = ADDRESS_ONE; - let pluginAddress = ADDRESS_SIX; let member = new ExtendedTokenVotingMember().withDefaultValues( - memberAddress, - pluginAddress + memberAddressHexString, + pluginEntityId ); + const oldDelegateeId = ADDRESS_TWO; + const newDelegateeAddress = Address.fromString(ADDRESS_THREE); + const newDelegateeId = generateEntityIdFromAddress(newDelegateeAddress); - let delegateeAddress = ADDRESS_THREE; let event = member.createEvent_DelegateChanged( - memberAddress, - ADDRESS_TWO, - delegateeAddress + memberAddressHexString, + oldDelegateeId, + newDelegateeId ); handleDelegateChanged(event); // assert // expected changes - member.delegatee = delegateeAddress.concat('_').concat(pluginAddress); + member.delegatee = generateMemberEntityId( + pluginAddress, + newDelegateeAddress + ); member.assertEntity(); assert.entityCount('TokenVotingMember', 3); }); test('it should update delegatee of an existing member', () => { - let memberAddress = ADDRESS_ONE; - let pluginAddress = ADDRESS_SIX; let member = new ExtendedTokenVotingMember().withDefaultValues( - memberAddress, - pluginAddress + memberAddressHexString, + pluginEntityId ); member.buildOrUpdate(); // there should be one member in the store assert.entityCount('TokenVotingMember', 1); - let fromDelegate = memberAddress; - let delegateeAddress = ADDRESS_TWO; + let delegateeAddress = Address.fromString(ADDRESS_TWO); + let delegateeId = generateEntityIdFromAddress(delegateeAddress); let event = member.createEvent_DelegateChanged( - memberAddress, - fromDelegate, - delegateeAddress + // member address is ADDRESS ONE + memberAddressHexString, + memberAddressHexString, + delegateeId ); handleDelegateChanged(event); // assert // expected changes - member.delegatee = delegateeAddress.concat('_').concat(pluginAddress); + member.delegatee = generateMemberEntityId( + pluginAddress, + delegateeAddress + ); member.assertEntity(); // there must be the second member in the store for the delegatee assert.entityCount('TokenVotingMember', 2); }); }); - describe('handleDelegateChanged', () => { + describe('handleDelegatevotesChanged', () => { test('it should create member for delegate address', () => { - let memberAddress = ADDRESS_ONE; - let pluginAddress = ADDRESS_SIX; let member = new ExtendedTokenVotingMember().withDefaultValues( - memberAddress, - pluginAddress + memberAddressHexString, + pluginEntityId ); member.votingPower = BigInt.fromString('100'); let event = member.createEvent_DelegateVotesChanged('100', '0'); handleDelegateVotesChanged(event); + member.delegatee = generateMemberEntityId(pluginAddress, memberAddress); member.assertEntity(); assert.entityCount('TokenVotingMember', 1); }); test('it should update delegateVotes of members', () => { - let memberAddress = ADDRESS_ONE; - let pluginAddress = ADDRESS_SIX; let member = new ExtendedTokenVotingMember().withDefaultValues( - memberAddress, - pluginAddress + memberAddressHexString, + pluginEntityId ); let newBalance = '111'; @@ -274,24 +504,26 @@ describe('Governance ERC20', () => { // assert // expected changes + member.delegatee = generateMemberEntityId(pluginAddress, memberAddress); member.votingPower = BigInt.fromString(newBalance); member.assertEntity(); assert.entityCount('TokenVotingMember', 1); }); - test('it should delete a member without voting power or balance', () => { - let memberOneAddress = ADDRESS_ONE; - let memberTwoAddress = ADDRESS_TWO; - let pluginAddress = ADDRESS_SIX; + test('it should delete a member without voting power and balance and not delegating to another address', () => { + const memberTwoAddress = Address.fromString(ADDRESS_TWO); + const memberTwoAddressHexString = + generateEntityIdFromAddress(memberTwoAddress); + let memberOne = new ExtendedTokenVotingMember().withDefaultValues( - memberOneAddress, - pluginAddress + memberAddressHexString, + pluginEntityId ); let memberTwo = new ExtendedTokenVotingMember().withDefaultValues( - memberTwoAddress, - pluginAddress + memberTwoAddressHexString, + pluginEntityId ); - /* member one has 100s token delegated to member two*/ + /* member one has 100 token delegated to member two*/ memberOne.balance = BigInt.fromString('100'); memberOne.votingPower = BigInt.fromString('0'); /* member two balance is 0 but has 100 voting power from the delegation of member one */ @@ -304,10 +536,16 @@ describe('Governance ERC20', () => { assert.entityCount('TokenVotingMember', 2); - // member one undelegates from member two + // member one un-delegates from member two let eventOne = memberOne.createEvent_DelegateVotesChanged('100'); let eventTwo = memberTwo.createEvent_DelegateVotesChanged('0'); + memberTwo.mockCall_delegatesCall( + DAO_TOKEN_ADDRESS, + memberTwoAddressHexString, + memberTwoAddressHexString + ); + handleDelegateVotesChanged(eventOne); handleDelegateVotesChanged(eventTwo); @@ -315,12 +553,144 @@ describe('Governance ERC20', () => { // expected changes memberOne.votingPower = BigInt.fromString('100'); memberOne.assertEntity(); - // member two should be deleted because it has no balance or voting power - assert.notInStore( + // member two should be deleted because it has no (balance and voting power) and not delegates to another address. + assert.notInStore('TokenVotingMember', memberTwo.id); + assert.entityCount('TokenVotingMember', 1); + }); + + test('it should not delete a member without voting power and balance, but delegating to another address', () => { + const memberTwoAddress = Address.fromString(ADDRESS_TWO); + const memberTwoAddressHexString = + generateEntityIdFromAddress(memberTwoAddress); + let memberOne = new ExtendedTokenVotingMember().withDefaultValues( + memberAddressHexString, + pluginEntityId + ); + let memberTwo = new ExtendedTokenVotingMember().withDefaultValues( + memberTwoAddressHexString, + pluginEntityId + ); + /* member one has 100 token delegated to member two*/ + memberOne.balance = BigInt.fromString('100'); + memberOne.votingPower = BigInt.fromString('0'); + /* member two balance is 0 but has 100 voting power from the delegation of member one */ + memberTwo.balance = BigInt.fromString('0'); + memberTwo.votingPower = BigInt.fromString('100'); + /* member three has 100 tokens and none delegated */ + + memberOne.buildOrUpdate(); + memberTwo.buildOrUpdate(); + + assert.entityCount('TokenVotingMember', 2); + + // member one un-delegates from member two + let eventOne = memberOne.createEvent_DelegateVotesChanged('100'); + let eventTwo = memberTwo.createEvent_DelegateVotesChanged('0'); + + memberTwo.mockCall_delegatesCall( + DAO_TOKEN_ADDRESS, + memberTwoAddressHexString, + memberAddressHexString + ); + + handleDelegateVotesChanged(eventOne); + handleDelegateVotesChanged(eventTwo); + + // assert + // expected changes + memberOne.votingPower = BigInt.fromString('100'); + memberOne.assertEntity(); + + assert.fieldEquals('TokenVotingMember', memberOne.id, 'id', memberOne.id); + // memberTwo should not be deleted because it has no (balance and voting power), but it delegates to another address. + assert.fieldEquals('TokenVotingMember', memberTwo.id, 'id', memberTwo.id); + assert.entityCount('TokenVotingMember', 2); + }); + + test("It should initialize with the user's existing voting power and delegation, if she has any", () => { + // constants + const STARTING_BALANCE = '10'; + const TRANSFER = '3'; + const REMAINING = '7'; + + // mocked calls + getBalanceOf( + DAO_TOKEN_ADDRESS, + fromAddress.toHexString(), + STARTING_BALANCE + ); + getBalanceOf(DAO_TOKEN_ADDRESS, toAddress.toHexString(), '0'); + getVotes(DAO_TOKEN_ADDRESS, fromAddress.toHexString(), STARTING_BALANCE); + getVotes(DAO_TOKEN_ADDRESS, toAddress.toHexString(), '0'); + getDelegatee( + DAO_TOKEN_ADDRESS, + fromAddress.toHexString(), + fromAddress.toHexString() + ); + getDelegatee(DAO_TOKEN_ADDRESS, toAddress.toHexString(), null); + + const memberEntityIdFrom = generateMemberEntityId( + pluginAddress, + fromAddress + ); + + const memberEntityIdFromSecondPlugin = generateMemberEntityId( + pluginAddressSecond, + Address.fromString(fromAddressHexString) + ); + + const memberEntityIdTo = generateMemberEntityId( + pluginAddressSecond, + Address.fromString(toAddressHexString) + ); + + const memberEntityIdToSecondPlugin = generateMemberEntityId( + pluginAddressSecond, + Address.fromString(toAddressHexString) + ); + + // delegate to self + const delegateChangedEvent = createNewDelegateChangedEvent( + fromAddressHexString, + fromAddressHexString, + fromAddressHexString, + DAO_TOKEN_ADDRESS + ); + + handleDelegateChanged(delegateChangedEvent); + + assert.fieldEquals( 'TokenVotingMember', - memberTwo.id + memberEntityIdFrom, + 'votingPower', + STARTING_BALANCE ); - assert.entityCount('TokenVotingMember', 1); + assert.fieldEquals( + 'TokenVotingMember', + memberEntityIdFrom, + 'delegatee', + memberEntityIdFrom + ); + + // now do the delegation in the context of the second plugin + setContext(pluginEntityIdSecond); + handleDelegateChanged(delegateChangedEvent); + + assert.fieldEquals( + 'TokenVotingMember', + memberEntityIdFromSecondPlugin, + 'votingPower', + STARTING_BALANCE + ); + assert.fieldEquals( + 'TokenVotingMember', + memberEntityIdFromSecondPlugin, + 'delegatee', + memberEntityIdFromSecondPlugin + ); + + // set the context back to the first plugin + setContext(pluginEntityId); }); }); }); diff --git a/packages/subgraph/tests/token/token-voting.test.ts b/packages/subgraph/tests/token/token-voting.test.ts index b9827fddb..b6ebd6d2b 100644 --- a/packages/subgraph/tests/token/token-voting.test.ts +++ b/packages/subgraph/tests/token/token-voting.test.ts @@ -1,14 +1,12 @@ -import {assert, clearStore, describe, test} from 'matchstick-as/assembly/index'; -import {bigInt, BigInt} from '@graphprotocol/graph-ts'; - import { handleVoteCast, handleProposalExecuted, handleVotingSettingsUpdated, _handleProposalCreated, - handleMembershipContractAnnounced + handleMembershipContractAnnounced, } from '../../src/packages/token/token-voting'; -import {VOTING_MODES, WRAPPED_ERC20_INTERFACE} from '../../src/utils/constants'; +import {VOTING_MODES} from '../../src/utils/constants'; +import {GOVERNANCE_WRAPPED_ERC20_INTERFACE_ID} from '../../src/utils/constants'; import { DAO_TOKEN_ADDRESS, STRING_DATA, @@ -16,20 +14,21 @@ import { ONE, ZERO, TWO, - ERC20_AMOUNT_FULL + ERC20_AMOUNT_FULL, } from '../constants'; - -import {createDummyActions} from '../utils'; import { ExtendedERC20Contract, ExtendedERC20WrapperContract, ExtendedTokenVotingPlugin, ExtendedTokenVotingProposal, ExtendedTokenVotingVote, - ExtendedTokenVotingVoter + ExtendedTokenVotingVoter, } from '../helpers/extended-schema'; +import {createDummyAction} from '@aragon/osx-commons-subgraph'; +import {bigInt, BigInt} from '@graphprotocol/graph-ts'; +import {assert, clearStore, describe, test} from 'matchstick-as/assembly/index'; -let actions = createDummyActions(DAO_TOKEN_ADDRESS, '0', '0x00000000'); +let actions = [createDummyAction(DAO_TOKEN_ADDRESS, '0', '0x00000000')]; test('Run TokenVoting (handleProposalCreated) mappings with mock event', () => { // create state @@ -57,10 +56,9 @@ test('Run TokenVoting (handleProposalCreated) mappings with mock event', () => { // expected changes proposal.creationBlockNumber = BigInt.fromString(ONE); proposal.votingMode = VOTING_MODES.get(parseInt(VOTING_MODE)) as string; - // check TokenVotingProposal + // // check TokenVotingProposal proposal.assertEntity(); - // check TokenVotingPlugin tokenVotingPlugin.assertEntity(); clearStore(); @@ -75,7 +73,7 @@ test('Run TokenVoting (handleVoteCast) mappings with mock event', () => { // check proposal entity proposal.assertEntity(); - // // create calls + // create calls proposal.yes = bigInt.fromString(ONE); proposal.mockCall_getProposal(actions); proposal.mockCall_totalVotingPower(); @@ -103,7 +101,7 @@ test('Run TokenVoting (handleVoteCast) mappings with mock event', () => { // check proposal // expected changes to the proposal entity proposal.castedVotingPower = BigInt.fromString(ONE); - proposal.potentiallyExecutable = false; + proposal.approvalReached = false; // assert proposal entity proposal.assertEntity(); @@ -147,7 +145,7 @@ test('Run TokenVoting (handleVoteCast) mappings with mock event', () => { handleVoteCast(event3); // expected changes to the proposal entity - proposal.potentiallyExecutable = true; + proposal.approvalReached = true; proposal.castedVotingPower = BigInt.fromString(TWO); proposal.assertEntity(); @@ -228,7 +226,10 @@ describe('handleMembershipContractAnnounced', () => { let erc20Contract = new ExtendedERC20Contract().withDefaultValues(); erc20Contract.mockCall_createTokenCalls(ERC20_AMOUNT_FULL); erc20Contract.mockCall_balanceOf(erc20Contract.id, ERC20_AMOUNT_FULL); - erc20Contract.mockCall_supportsInterface(WRAPPED_ERC20_INTERFACE, false); + erc20Contract.mockCall_supportsInterface( + GOVERNANCE_WRAPPED_ERC20_INTERFACE_ID, + false + ); erc20Contract.mockCall_supportsInterface('ffffffff', false); tokenVotingPlugin.token = erc20Contract.id; @@ -249,10 +250,14 @@ describe('handleMembershipContractAnnounced', () => { // create entities let tokenVotingPlugin = new ExtendedTokenVotingPlugin().withDefaultValues(); let erc20Contract = new ExtendedERC20Contract().withDefaultValues(); - let erc20WrappedContract = new ExtendedERC20WrapperContract().withDefaultValues(); + let erc20WrappedContract = + new ExtendedERC20WrapperContract().withDefaultValues(); erc20Contract.mockCall_createTokenCalls(ERC20_AMOUNT_FULL); erc20Contract.mockCall_balanceOf(erc20Contract.id, ERC20_AMOUNT_FULL); - erc20Contract.mockCall_supportsInterface(WRAPPED_ERC20_INTERFACE, false); + erc20Contract.mockCall_supportsInterface( + GOVERNANCE_WRAPPED_ERC20_INTERFACE_ID, + false + ); erc20Contract.mockCall_supportsInterface('ffffffff', false); erc20WrappedContract.mockCall_createTokenCalls(ERC20_AMOUNT_FULL); @@ -261,7 +266,7 @@ describe('handleMembershipContractAnnounced', () => { ERC20_AMOUNT_FULL ); erc20WrappedContract.mockCall_supportsInterface( - WRAPPED_ERC20_INTERFACE, + GOVERNANCE_WRAPPED_ERC20_INTERFACE_ID, true ); erc20WrappedContract.mockCall_supportsInterface('ffffffff', false); diff --git a/packages/subgraph/tests/token/utils.ts b/packages/subgraph/tests/token/utils.ts index 4b69c4125..1f3de5595 100644 --- a/packages/subgraph/tests/token/utils.ts +++ b/packages/subgraph/tests/token/utils.ts @@ -1,14 +1,17 @@ -import {Address, BigInt, Bytes, ethereum} from '@graphprotocol/graph-ts'; -import {createMockedFunction, newMockEvent} from 'matchstick-as'; - +import {TokenVotingMember, TokenVotingProposal} from '../../generated/schema'; +import { + DelegateChanged, + DelegateVotesChanged, +} from '../../generated/templates/GovernanceERC20/GovernanceERC20'; +import {Transfer as ERC20TransferEvent} from '../../generated/templates/TokenVoting/ERC20'; import { VotingSettingsUpdated, VoteCast, ProposalCreated, ProposalExecuted, - MembershipContractAnnounced + MembershipContractAnnounced, } from '../../generated/templates/TokenVoting/TokenVoting'; -import {TokenVotingMember, TokenVotingProposal} from '../../generated/schema'; +import {generateMemberEntityId} from '../../src/utils/ids'; import { ADDRESS_ONE, DAO_ADDRESS, @@ -23,13 +26,11 @@ import { SNAPSHOT_BLOCK, TOTAL_VOTING_POWER, CREATED_AT, - ALLOW_FAILURE_MAP + ALLOW_FAILURE_MAP, + DEFAULT_MOCK_EVENT_ADDRESS, } from '../constants'; -import {Transfer as ERC20TransferEvent} from '../../generated/templates/TokenVoting/ERC20'; -import { - DelegateChanged, - DelegateVotesChanged -} from '../../generated/templates/GovernanceERC20/GovernanceERC20'; +import {Address, BigInt, Bytes, ethereum} from '@graphprotocol/graph-ts'; +import {createMockedFunction, newMockEvent} from 'matchstick-as'; // events @@ -290,6 +291,20 @@ export function getProposalCountCall( .returns([ethereum.Value.fromSignedBigInt(BigInt.fromString(returns))]); } +export function delegatesCall( + contractAddress: string, + account: string, + returns: string +): void { + createMockedFunction( + Address.fromString(contractAddress), + 'delegates', + 'delegates(address):(address)' + ) + .withArgs([ethereum.Value.fromAddress(Address.fromString(account))]) + .returns([ethereum.Value.fromAddress(Address.fromString(returns))]); +} + // state export function createTokenVotingProposalEntityState( @@ -331,12 +346,13 @@ export function createTokenVotingProposalEntityState( tokenVotingProposal.startDate = BigInt.fromString(startDate); tokenVotingProposal.endDate = BigInt.fromString(endDate); tokenVotingProposal.snapshotBlock = BigInt.fromString(snapshotBlock); + tokenVotingProposal.isSignaling = false; tokenVotingProposal.totalVotingPower = BigInt.fromString(totalVotingPower); tokenVotingProposal.allowFailureMap = BigInt.fromString(allowFailureMap); tokenVotingProposal.createdAt = BigInt.fromString(createdAt); tokenVotingProposal.creationBlockNumber = creationBlockNumber; - tokenVotingProposal.potentiallyExecutable = executable; + tokenVotingProposal.approvalReached = executable; tokenVotingProposal.earlyExecutable = earlyExecutable; tokenVotingProposal.save(); @@ -349,8 +365,21 @@ export function createNewERC20TransferEvent( to: string, amount: string ): ERC20TransferEvent { - let transferEvent = changetype(newMockEvent()); + return createNewERC20TransferEventWithAddress( + from, + to, + amount, + DEFAULT_MOCK_EVENT_ADDRESS + ); +} +export function createNewERC20TransferEventWithAddress( + from: string, + to: string, + amount: string, + contractAddress: string +): ERC20TransferEvent { + let transferEvent = changetype(newMockEvent()); let fromParam = new ethereum.EventParam( 'from', ethereum.Value.fromAddress(Address.fromString(from)) @@ -363,11 +392,10 @@ export function createNewERC20TransferEvent( 'amount', ethereum.Value.fromSignedBigInt(BigInt.fromString(amount)) ); - + transferEvent.address = Address.fromString(contractAddress); transferEvent.parameters.push(fromParam); transferEvent.parameters.push(toParam); transferEvent.parameters.push(amountParam); - return transferEvent; } @@ -376,16 +404,50 @@ export function createTokenVotingMember( plugin: string, balance: string ): string { - const fromUserId = address.concat('_').concat(plugin); + const memberEntityId = generateMemberEntityId( + Address.fromString(plugin), // uses other plugin address to make sure that the code reuses the entity + Address.fromString(address) + ); - const user = new TokenVotingMember(fromUserId); + const user = new TokenVotingMember(memberEntityId); user.address = Address.fromString(address); user.plugin = plugin; // uses other plugin address to make sure that the code reuses the entity user.balance = BigInt.fromString(balance); - user.delegatee = fromUserId; + user.delegatee = memberEntityId; user.votingPower = BigInt.zero(); user.save(); - return fromUserId; + return memberEntityId; +} + +export function getDelegatee( + contractAddress: string, + account: string, + returns: string | null +): void { + const returnsValue = returns + ? ethereum.Value.fromAddress(Address.fromString(returns)) + : ethereum.Value.fromAddress(Address.zero()); + createMockedFunction( + Address.fromString(contractAddress), + 'delegates', + 'delegates(address):(address)' + ) + .withArgs([ethereum.Value.fromAddress(Address.fromString(account))]) + .returns([returnsValue]); +} + +export function getVotes( + contractAddress: string, + account: string, + returns: string +): void { + createMockedFunction( + Address.fromString(contractAddress), + 'getVotes', + 'getVotes(address):(uint256)' + ) + .withArgs([ethereum.Value.fromAddress(Address.fromString(account))]) + .returns([ethereum.Value.fromSignedBigInt(BigInt.fromString(returns))]); } diff --git a/packages/subgraph/tests/utils.ts b/packages/subgraph/tests/utils.ts index af284824c..4d5f33897 100644 --- a/packages/subgraph/tests/utils.ts +++ b/packages/subgraph/tests/utils.ts @@ -1,4 +1,4 @@ -import {Address, BigInt, Bytes, ethereum} from '@graphprotocol/graph-ts'; +import {Address, BigInt, ethereum} from '@graphprotocol/graph-ts'; import {createMockedFunction} from 'matchstick-as/assembly/index'; export function createMockGetter( @@ -16,78 +16,6 @@ export function createMockGetter( .returns(returns); } -export function createERC1155TokenCalls( - contractAddress: string, - tokenId: string, - uri: string -): void { - createMockedFunction( - Address.fromString(contractAddress), - 'uri', - 'uri(uint256):(string)' - ) - .withArgs([ethereum.Value.fromUnsignedBigInt(BigInt.fromString(tokenId))]) - .returns([ethereum.Value.fromString(uri)]); -} - -export function createTokenCalls( - contractAddress: string, - name: string, - symbol: string, - decimals: string | null, - totalSupply: string | null -): void { - createMockGetter(contractAddress, 'name', 'name():(string)', [ - ethereum.Value.fromString(name) - ]); - - createMockGetter(contractAddress, 'symbol', 'symbol():(string)', [ - ethereum.Value.fromString(symbol) - ]); - - if (decimals) { - createMockGetter(contractAddress, 'decimals', 'decimals():(uint8)', [ - ethereum.Value.fromUnsignedBigInt(BigInt.fromString(decimals)) - ]); - } - - if (totalSupply) { - createMockGetter( - contractAddress, - 'totalSupply', - 'totalSupply():(uint256)', - [ethereum.Value.fromUnsignedBigInt(BigInt.fromString(totalSupply))] - ); - } -} - -export function createWrappedTokenCalls( - contractAddress: string, - name: string, - symbol: string, - underlyingTokenAddress: string, - totalSupply: string | null -): void { - createTokenCalls(contractAddress, name, symbol, '18', totalSupply); - createMockGetter(contractAddress, 'underlying', 'underlying():(address)', [ - ethereum.Value.fromAddress(Address.fromString(underlyingTokenAddress)) - ]); -} - -export function createDummyActions( - address: string, - value: string, - data: string -): ethereum.Tuple[] { - let tuple = new ethereum.Tuple(); - - tuple.push(ethereum.Value.fromAddress(Address.fromString(address))); - tuple.push(ethereum.Value.fromSignedBigInt(BigInt.fromString(value))); - tuple.push(ethereum.Value.fromBytes(Bytes.fromHexString(data) as Bytes)); - - return [tuple]; -} - export function createGetProposalCall( contractAddress: string, proposalId: string, @@ -141,7 +69,7 @@ export function createGetProposalCall( 'getProposal(uint256):(bool,bool,(uint8,uint32,uint64,uint64,uint64,uint256),(uint256,uint256,uint256),(address,uint256,bytes)[],uint256)' ) .withArgs([ - ethereum.Value.fromUnsignedBigInt(BigInt.fromString(proposalId)) + ethereum.Value.fromUnsignedBigInt(BigInt.fromString(proposalId)), ]) .returns([ ethereum.Value.fromBoolean(open), @@ -155,7 +83,7 @@ export function createGetProposalCall( ethereum.Value.fromTupleArray(actions), - ethereum.Value.fromUnsignedBigInt(BigInt.fromString(allowFailureMap)) + ethereum.Value.fromUnsignedBigInt(BigInt.fromString(allowFailureMap)), ]); } @@ -171,9 +99,9 @@ export function createTotalVotingPowerCall( 'totalVotingPower(uint256):(uint256)' ) .withArgs([ - ethereum.Value.fromUnsignedBigInt(BigInt.fromString(blockNumber)) + ethereum.Value.fromUnsignedBigInt(BigInt.fromString(blockNumber)), ]) .returns([ - ethereum.Value.fromUnsignedBigInt(BigInt.fromString(totalVotingPower)) + ethereum.Value.fromUnsignedBigInt(BigInt.fromString(totalVotingPower)), ]); } diff --git a/packages/subgraph/tests/utils/utils.test.ts b/packages/subgraph/tests/utils/utils.test.ts index be0f4c27f..7bd018a67 100644 --- a/packages/subgraph/tests/utils/utils.test.ts +++ b/packages/subgraph/tests/utils/utils.test.ts @@ -1,6 +1,3 @@ -import {BigInt} from '@graphprotocol/graph-ts'; - -import {assert, describe, test} from 'matchstick-as/assembly/index'; import {bigIntToBytes32} from '../../src/utils/bytes'; import { ZERO, @@ -10,11 +7,13 @@ import { ZERO_BYTES32, ONE_BYTES32, HALF_UINT256_BYTES32, - MAX_UINT256_BYTES32 + MAX_UINT256_BYTES32, } from '../constants'; +import {BigInt} from '@graphprotocol/graph-ts'; +import {assert, describe, test} from 'matchstick-as/assembly/index'; -describe('Test bytes', function() { - test('`bigIntToBytes32` with a range of `bigInt`s', function() { +describe('Test bytes', function () { + test('`bigIntToBytes32` with a range of `bigInt`s', function () { const MAX_UINT256 = BigInt.fromString(MAX_UINT256_NUMBER_STRING); assert.stringEquals(bigIntToBytes32(BigInt.fromString(ZERO)), ZERO_BYTES32); diff --git a/yarn.lock b/yarn.lock index 3d64514b2..00fda09d7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,13 @@ # yarn lockfile v1 +"@aragon/osx-commons-subgraph@^0.0.4": + version "0.0.4" + resolved "https://registry.yarnpkg.com/@aragon/osx-commons-subgraph/-/osx-commons-subgraph-0.0.4.tgz#2aa52f3089d21189c9152d2f3d14c0d7c66d129f" + integrity sha512-cqhusJ3HNvMx+t9lXfN+Hy/5ipefNs1Tdxe+y0GvD4qgBMVU4tCbsxOpB9U2JEJNBCzFQj4E/872FFLpIErB4w== + dependencies: + "@graphprotocol/graph-ts" "0.31.0" + "@aragon/osx-ethers-v1.2.0@npm:@aragon/osx-ethers@1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@aragon/osx-ethers/-/osx-ethers-1.2.0.tgz#b7e58714394183372ddf25d97cfe326f18ce6f1d" @@ -35,6 +42,65 @@ dependencies: "@babel/highlight" "^7.16.7" +"@babel/code-frame@^7.22.13", "@babel/code-frame@^7.23.5": + version "7.24.2" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.2.tgz#718b4b19841809a58b29b68cde80bc5e1aa6d9ae" + integrity sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ== + dependencies: + "@babel/highlight" "^7.24.2" + picocolors "^1.0.0" + +"@babel/generator@7.17.7": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.17.7.tgz#8da2599beb4a86194a3b24df6c085931d9ee45ad" + integrity sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w== + dependencies: + "@babel/types" "^7.17.0" + jsesc "^2.5.1" + source-map "^0.5.0" + +"@babel/generator@^7.23.0": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.1.tgz#e67e06f68568a4ebf194d1c6014235344f0476d0" + integrity sha512-DfCRfZsBcrPEHUfuBMgbJ1Ut01Y/itOs+hY2nFLgqsqXd52/iSiVq5TITtUasIUgm+IIKdY2/1I7auiQOEeC9A== + dependencies: + "@babel/types" "^7.24.0" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^2.5.1" + +"@babel/helper-environment-visitor@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" + integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== + +"@babel/helper-function-name@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" + integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== + dependencies: + "@babel/template" "^7.22.15" + "@babel/types" "^7.23.0" + +"@babel/helper-hoist-variables@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" + integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-split-export-declaration@^7.22.6": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" + integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-string-parser@^7.23.4": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz#f99c36d3593db9540705d0739a1f10b5e20c696e" + integrity sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ== + "@babel/helper-validator-identifier@^7.15.7": version "7.15.7" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389" @@ -45,6 +111,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + "@babel/highlight@^7.10.4", "@babel/highlight@^7.16.0": version "7.16.0" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.0.tgz#6ceb32b2ca4b8f5f361fb7fd821e3fddf4a1725a" @@ -63,6 +134,21 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@babel/highlight@^7.24.2": + version "7.24.2" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.2.tgz#3f539503efc83d3c59080a10e6634306e0370d26" + integrity sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" + +"@babel/parser@^7.20.5", "@babel/parser@^7.23.0", "@babel/parser@^7.24.0": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.1.tgz#1e416d3627393fab1cb5b0f2f1796a100ae9133a" + integrity sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg== + "@babel/runtime@^7.4.4": version "7.18.0" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.0.tgz#6d77142a19cb6088f0af662af1ada37a604d34ae" @@ -70,6 +156,48 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/template@^7.22.15": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.0.tgz#c6a524aa93a4a05d66aaf31654258fae69d87d50" + integrity sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA== + dependencies: + "@babel/code-frame" "^7.23.5" + "@babel/parser" "^7.24.0" + "@babel/types" "^7.24.0" + +"@babel/traverse@7.23.2": + version "7.23.2" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8" + integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw== + dependencies: + "@babel/code-frame" "^7.22.13" + "@babel/generator" "^7.23.0" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.23.0" + "@babel/types" "^7.23.0" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/types@7.17.0": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.17.0.tgz#a826e368bccb6b3d84acd76acad5c0d87342390b" + integrity sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw== + dependencies: + "@babel/helper-validator-identifier" "^7.16.7" + to-fast-properties "^2.0.0" + +"@babel/types@^7.17.0", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.0.tgz#3b951f435a92e7333eba05b7566fd297960ea1bf" + integrity sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w== + dependencies: + "@babel/helper-string-parser" "^7.23.4" + "@babel/helper-validator-identifier" "^7.22.20" + to-fast-properties "^2.0.0" + "@cspotcode/source-map-support@^0.8.0": version "0.8.1" resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" @@ -1309,6 +1437,13 @@ which "2.0.2" yaml "1.10.2" +"@graphprotocol/graph-ts@0.31.0": + version "0.31.0" + resolved "https://registry.yarnpkg.com/@graphprotocol/graph-ts/-/graph-ts-0.31.0.tgz#730668c0369828b31bef81e8d9bc66b9b48e3480" + integrity sha512-xreRVM6ho2BtolyOh2flDkNoGZximybnzUnF53zJVp0+Ed0KnAlO1/KOCUYw06euVI9tk0c9nA2Z/D5SIQV2Rg== + dependencies: + assemblyscript "0.19.10" + "@graphprotocol/graph-ts@^0.27.0": version "0.27.0" resolved "https://registry.yarnpkg.com/@graphprotocol/graph-ts/-/graph-ts-0.27.0.tgz#948fe1716f6082964a01a63a19bcbf9ac44e06ff" @@ -1370,12 +1505,31 @@ dependencies: multiformats "^9.5.4" +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" + integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + "@jridgewell/resolve-uri@^3.0.3": version "3.1.1" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== -"@jridgewell/sourcemap-codec@^1.4.10": +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": version "1.4.15" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== @@ -1388,6 +1542,14 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + "@leichtgewicht/ip-codec@^2.0.1": version "2.0.4" resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" @@ -2005,6 +2167,11 @@ dependencies: antlr4ts "^0.5.0-alpha.4" +"@solidity-parser/parser@^0.17.0": + version "0.17.0" + resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.17.0.tgz#52a2fcc97ff609f72011014e4c5b485ec52243ef" + integrity sha512-Nko8R0/kUo391jsEHHxrGM07QFdnPGvlmox4rmH0kNiNAashItAilhy4Mv4pK5gQmW5f4sXAF58fwJbmlkGcVw== + "@szmarczak/http-timer@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" @@ -2012,6 +2179,18 @@ dependencies: defer-to-connect "^1.0.1" +"@trivago/prettier-plugin-sort-imports@^4.2.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-4.3.0.tgz#725f411646b3942193a37041c84e0b2116339789" + integrity sha512-r3n0onD3BTOVUNPhR4lhVK4/pABGpbA7bW3eumZnYdKaHkf1qEC+Mag6DPbGNuuh0eG8AaYj+YqmVHSiGslaTQ== + dependencies: + "@babel/generator" "7.17.7" + "@babel/parser" "^7.20.5" + "@babel/traverse" "7.23.2" + "@babel/types" "7.17.0" + javascript-natural-sort "0.7.1" + lodash "^4.17.21" + "@truffle/abi-utils@^0.2.13": version "0.2.13" resolved "https://registry.yarnpkg.com/@truffle/abi-utils/-/abi-utils-0.2.13.tgz#63b7f5e5b61a86e563b2ea0c93a39b094086d205" @@ -4112,7 +4291,7 @@ debug@4, debug@^4.0.1, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: dependencies: ms "2.1.2" -debug@4.3.4, debug@^4.3.3, debug@^4.3.4: +debug@4.3.4, debug@^4.1.0, debug@^4.3.3, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -5968,7 +6147,7 @@ global@~4.4.0: min-document "^2.19.0" process "^0.11.10" -globals@^11.7.0: +globals@^11.1.0, globals@^11.7.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== @@ -7246,6 +7425,11 @@ jake@^10.6.1, jake@^10.8.5: filelist "^1.0.4" minimatch "^3.1.2" +javascript-natural-sort@0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz#f9e2303d4507f6d74355a73664d1440fb5a0ef59" + integrity sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw== + jayson@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jayson/-/jayson-4.0.0.tgz#145a0ced46f900934c9b307e1332bcb0c7dbdb17" @@ -7307,6 +7491,11 @@ jsbn@~0.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + json-buffer@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" @@ -7728,10 +7917,10 @@ match-all@^1.2.6: resolved "https://registry.yarnpkg.com/match-all/-/match-all-1.2.6.tgz#66d276ad6b49655551e63d3a6ee53e8be0566f8d" integrity sha512-0EESkXiTkWzrQQntBu2uzKvLu6vVkUGz40nGPbSZuegcfE5UuSzNjLaIu76zJWuaT/2I3Z/8M06OlUOZLGwLlQ== -matchstick-as@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/matchstick-as/-/matchstick-as-0.5.1.tgz#dd2983811e1f346a1d7607c82a354f12a93b8e2b" - integrity sha512-4mClFCu+wGoKH0V7u058U2lel7IJGcAABtPuq6u1jEUmV86ATWQWNw1x6S0zNUG5GJUBuq6Ghb0qBI1Cjx+KoA== +matchstick-as@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/matchstick-as/-/matchstick-as-0.5.2.tgz#6a6dde02d1d939c32458bd67bac688891a07a34c" + integrity sha512-fb1OVphDKEvJY06Ue02Eh1CNncuW95vp6b8tNAP7UIqplICSLoU/zgN6U7ge7R0upsoO78C7CRi4EyK/7Jxz7g== dependencies: wabt "1.0.24" @@ -8807,6 +8996,11 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3: version "2.3.0" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" @@ -8887,6 +9081,15 @@ prettier-plugin-solidity@^1.1.1: semver "^7.3.8" solidity-comments-extractor "^0.0.7" +prettier-plugin-solidity@^1.1.3: + version "1.3.1" + resolved "https://registry.yarnpkg.com/prettier-plugin-solidity/-/prettier-plugin-solidity-1.3.1.tgz#59944d3155b249f7f234dee29f433524b9a4abcf" + integrity sha512-MN4OP5I2gHAzHZG1wcuJl0FsLS3c4Cc5494bbg+6oQWBPuEamjwDvmGfFMZ6NFzsh3Efd9UUxeT7ImgjNH4ozA== + dependencies: + "@solidity-parser/parser" "^0.17.0" + semver "^7.5.4" + solidity-comments-extractor "^0.0.8" + prettier@1.19.1, prettier@^1.14.3: version "1.19.1" resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" @@ -8907,6 +9110,11 @@ prettier@^2.4.1: resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.1.tgz#4e1fd11c34e2421bc1da9aea9bd8127cd0a35efc" integrity sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg== +prettier@^2.8.8: + version "2.8.8" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" + integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== + printj@~1.1.0: version "1.1.2" resolved "https://registry.yarnpkg.com/printj/-/printj-1.1.2.tgz#d90deb2975a8b9f600fb3a1c94e3f4c53c78a222" @@ -9595,6 +9803,13 @@ semver@^7.3.8: dependencies: lru-cache "^6.0.0" +semver@^7.5.4: + version "7.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" + integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== + dependencies: + lru-cache "^6.0.0" + send@0.17.1: version "0.17.1" resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" @@ -9857,6 +10072,11 @@ solidity-comments-extractor@^0.0.7: resolved "https://registry.yarnpkg.com/solidity-comments-extractor/-/solidity-comments-extractor-0.0.7.tgz#99d8f1361438f84019795d928b931f4e5c39ca19" integrity sha512-wciNMLg/Irp8OKGrh3S2tfvZiZ0NEyILfcRCXCD4mp7SgK/i9gzLfhY2hY7VMCQJ3kH9UB9BzNdibIVMchzyYw== +solidity-comments-extractor@^0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/solidity-comments-extractor/-/solidity-comments-extractor-0.0.8.tgz#f6e148ab0c49f30c1abcbecb8b8df01ed8e879f8" + integrity sha512-htM7Vn6LhHreR+EglVMd2s+sZhcXAirB1Zlyrv5zBuTxieCvjfnRpd7iZk75m/u6NOlEyQ94C6TWbBn2cY7w8g== + solidity-coverage@^0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/solidity-coverage/-/solidity-coverage-0.8.2.tgz#bc39604ab7ce0a3fa7767b126b44191830c07813" @@ -9899,6 +10119,11 @@ source-map-support@^0.5.13, source-map-support@^0.5.17, source-map-support@^0.5. buffer-from "^1.0.0" source-map "^0.6.0" +source-map@^0.5.0: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== + source-map@^0.6.0, source-map@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" @@ -10418,6 +10643,11 @@ to-buffer@^1.1.1: resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80" integrity sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg== +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + to-readable-stream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771"