diff --git a/.changeset/nine-seas-dream.md b/.changeset/nine-seas-dream.md new file mode 100644 index 0000000000..6b9f135b78 --- /dev/null +++ b/.changeset/nine-seas-dream.md @@ -0,0 +1,6 @@ +--- +"@human-protocol/sdk": major +"@human-protocol/python-sdk": major +--- + +Updated SDK to use a dedicated HMT stats subgraph endpoint for HMT statistics methods and removed `totalAmountPaid` and `averageAmountPerWorker` from `IDailyPayment` diff --git a/.github/workflows/cd-subgraph.yaml b/.github/workflows/cd-subgraph.yaml index 4bd0f85114..0472bc0637 100644 --- a/.github/workflows/cd-subgraph.yaml +++ b/.github/workflows/cd-subgraph.yaml @@ -55,7 +55,7 @@ jobs: - name: Generate and build Subgraph if: steps.filter_networks.outputs.continue == 'true' run: yarn generate && yarn build - working-directory: ./packages/sdk/typescript/subgraph + working-directory: ./packages/sdk/typescript/subgraph/human-protocol env: NETWORK: ${{ matrix.network.name }} - name: Authenticate & Deploy @@ -64,7 +64,7 @@ jobs: API_KEY: ${{ secrets.HP_GRAPH_API_KEY }} NETWORK: ${{ matrix.network.name }} LABEL: ${{ github.event.inputs.label }} - working-directory: ./packages/sdk/typescript/subgraph + working-directory: ./packages/sdk/typescript/subgraph/human-protocol run: | yarn dlx @graphprotocol/graph-cli@0.71.2 \ auth --studio "$API_KEY" diff --git a/.github/workflows/ci-test-subgraph.yaml b/.github/workflows/ci-test-subgraph.yaml index d569f765f2..b12d2792d7 100644 --- a/.github/workflows/ci-test-subgraph.yaml +++ b/.github/workflows/ci-test-subgraph.yaml @@ -7,11 +7,20 @@ on: - "packages/core/**" - "packages/sdk/typescript/subgraph/**" +permissions: + contents: read + jobs: subgraph-test: - name: Subgraph Test + name: Subgraph Test (${{ matrix.workspace }}) # TODO: Use ubuntu-latest when graph binary is not failing on ubuntu 24.04 runs-on: ubuntu-22.04 + strategy: + matrix: + workspace: + - "@tools/subgraph" + - "@tools/subgraph-hmt-stats" + fail-fast: true steps: - uses: actions/checkout@v6 - uses: actions/setup-node@v6 @@ -19,10 +28,10 @@ jobs: node-version-file: .nvmrc cache: yarn - name: Install dependencies - run: yarn workspaces focus @tools/subgraph + run: yarn workspaces focus @tools/subgraph @tools/subgraph-hmt-stats - name: Build core package run: yarn workspace @human-protocol/core build - name: Generate manifest for Polygon for tests - run: NETWORK=polygon yarn workspace @tools/subgraph generate + run: NETWORK=polygon yarn workspace ${{ matrix.workspace }} generate - name: Run subgraph test - run: yarn workspace @tools/subgraph test + run: yarn workspace ${{ matrix.workspace }} test diff --git a/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts b/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts index cdf7d3a25a..f90bfe172e 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.service.spec.ts @@ -791,11 +791,8 @@ describe('JobService', () => { jobEntity.userId.toString(), expect.objectContaining({ recordingOracle: jobEntity.recordingOracle, - recordingOracleFee: 1n, reputationOracle: jobEntity.reputationOracle, - reputationOracleFee: 1n, exchangeOracle: jobEntity.exchangeOracle, - exchangeOracleFee: 1n, manifest: jobEntity.manifestUrl, manifestHash: jobEntity.manifestHash, }), diff --git a/packages/apps/job-launcher/server/src/modules/job/job.service.ts b/packages/apps/job-launcher/server/src/modules/job/job.service.ts index efe70a86f7..fd72abcec9 100644 --- a/packages/apps/job-launcher/server/src/modules/job/job.service.ts +++ b/packages/apps/job-launcher/server/src/modules/job/job.service.ts @@ -317,20 +317,8 @@ export class JobService { const escrowConfig = { recordingOracle: jobEntity.recordingOracle, - recordingOracleFee: await this.getOracleFee( - jobEntity.recordingOracle, - jobEntity.chainId, - ), reputationOracle: jobEntity.reputationOracle, - reputationOracleFee: await this.getOracleFee( - jobEntity.reputationOracle, - jobEntity.chainId, - ), exchangeOracle: jobEntity.exchangeOracle, - exchangeOracleFee: await this.getOracleFee( - jobEntity.exchangeOracle, - jobEntity.chainId, - ), manifest: jobEntity.manifestUrl, manifestHash: jobEntity.manifestHash, }; diff --git a/packages/core/contracts/Escrow.sol b/packages/core/contracts/Escrow.sol index 0025f402a2..d4ab130a6f 100644 --- a/packages/core/contracts/Escrow.sol +++ b/packages/core/contracts/Escrow.sol @@ -8,6 +8,13 @@ import '@openzeppelin/contracts/utils/ReentrancyGuard.sol'; import './interfaces/IEscrow.sol'; +interface IKVStore { + function get( + address _account, + string memory _key + ) external view returns (string memory); +} + struct Fees { uint256 reputation; uint256 recording; @@ -23,6 +30,8 @@ contract Escrow is IEscrow, ReentrancyGuard { using SafeERC20 for IERC20; string private constant ERROR_ZERO_ADDRESS = 'Zero address'; + string private constant FEE_KEY = 'fee'; + uint8 private constant MAX_ORACLE_FEE = 25; uint32 private constant BULK_MAX_COUNT = 100; event IntermediateStorage(string url, string hash); @@ -34,6 +43,16 @@ contract Escrow is IEscrow, ReentrancyGuard { address recordingOracle, address exchangeOracle ); + event PendingV3( + string manifest, + string hash, + address reputationOracle, + address recordingOracle, + address exchangeOracle, + uint8 reputationOracleFeePercentage, + uint8 recordingOracleFeePercentage, + uint8 exchangeOracleFeePercentage + ); event BulkTransfer( uint256 indexed txId, address[] recipients, @@ -72,12 +91,13 @@ contract Escrow is IEscrow, ReentrancyGuard { address public immutable admin; address public immutable escrowFactory; address public immutable token; + address public immutable kvstore; uint8 public reputationOracleFeePercentage; uint8 public recordingOracleFeePercentage; uint8 public exchangeOracleFeePercentage; - string public manifestUrl; + string public manifest; string public manifestHash; string public intermediateResultsUrl; string public intermediateResultsHash; @@ -94,21 +114,25 @@ contract Escrow is IEscrow, ReentrancyGuard { * @param _launcher Creator of the escrow. * @param _admin Admin address for the escrow. * @param _duration Escrow lifetime (seconds). + * @param _kvstore KVStore contract address. */ constructor( address _token, address _launcher, address _admin, - uint256 _duration + uint256 _duration, + address _kvstore ) { require(_launcher != address(0), ERROR_ZERO_ADDRESS); require(_admin != address(0), ERROR_ZERO_ADDRESS); require(_token != address(0), ERROR_ZERO_ADDRESS); + require(_kvstore != address(0), ERROR_ZERO_ADDRESS); require(_duration > 0, 'Duration is 0'); token = _token; launcher = _launcher; admin = _admin; + kvstore = _kvstore; escrowFactory = msg.sender; status = EscrowStatuses.Launched; @@ -135,30 +159,24 @@ contract Escrow is IEscrow, ReentrancyGuard { * @param _reputationOracle Address of the reputation oracle. * @param _recordingOracle Address of the recording oracle. * @param _exchangeOracle Address of the exchange oracle. - * @param _reputationOracleFeePercentage Fee percentage for the reputation oracle. - * @param _recordingOracleFeePercentage Fee percentage for the recording oracle. - * @param _exchangeOracleFeePercentage Fee percentage for the exchange oracle. - * @param _url URL for the escrow manifest. - * @param _hash Hash of the escrow manifest. + * @param _manifest Manifest or URL for the escrow manifest. + * @param _manifestHash Hash of the escrow manifest. */ function setup( address _reputationOracle, address _recordingOracle, address _exchangeOracle, - uint8 _reputationOracleFeePercentage, - uint8 _recordingOracleFeePercentage, - uint8 _exchangeOracleFeePercentage, - string calldata _url, - string calldata _hash + string calldata _manifest, + string calldata _manifestHash ) external override adminLauncherOrFactory notExpired { require(_reputationOracle != address(0), 'Invalid reputation oracle'); require(_recordingOracle != address(0), 'Invalid recording oracle'); require(_exchangeOracle != address(0), 'Invalid exchange oracle'); - uint256 totalFeePercentage = _reputationOracleFeePercentage + - _recordingOracleFeePercentage + - _exchangeOracleFeePercentage; - require(totalFeePercentage <= 100, 'Percentage out of bounds'); + uint8 _reputationOracleFeePercentage = _getOracleFee(_reputationOracle); + uint8 _recordingOracleFeePercentage = _getOracleFee(_recordingOracle); + uint8 _exchangeOracleFeePercentage = _getOracleFee(_exchangeOracle); + require(status == EscrowStatuses.Launched, 'Wrong status'); reputationOracle = _reputationOracle; @@ -169,23 +187,45 @@ contract Escrow is IEscrow, ReentrancyGuard { recordingOracleFeePercentage = _recordingOracleFeePercentage; exchangeOracleFeePercentage = _exchangeOracleFeePercentage; - manifestUrl = _url; - manifestHash = _hash; + manifest = _manifest; + manifestHash = _manifestHash; status = EscrowStatuses.Pending; remainingFunds = getBalance(); require(remainingFunds > 0, 'Zero balance'); - emit PendingV2( - _url, - _hash, + emit PendingV3( + _manifest, + _manifestHash, _reputationOracle, _recordingOracle, - _exchangeOracle + _exchangeOracle, + _reputationOracleFeePercentage, + _recordingOracleFeePercentage, + _exchangeOracleFeePercentage ); emit Fund(remainingFunds); } + function _getOracleFee(address _oracle) private view returns (uint8) { + return _parseUint8(IKVStore(kvstore).get(_oracle, FEE_KEY)); + } + + function _parseUint8(string memory _value) private pure returns (uint8) { + bytes memory valueBytes = bytes(_value); + require(valueBytes.length > 0, 'Invalid oracle fee'); + + uint256 parsedValue = 0; + for (uint256 i = 0; i < valueBytes.length; i++) { + uint8 c = uint8(valueBytes[i]); + require(c >= 48 && c <= 57, 'Invalid oracle fee'); + parsedValue = parsedValue * 10 + (c - 48); + require(parsedValue <= MAX_ORACLE_FEE, 'Percentage out of bounds'); + } + + return uint8(parsedValue); + } + /** * @dev Initiates a cancellation request. If expired, it finalizes immediately. */ diff --git a/packages/core/contracts/EscrowFactory.sol b/packages/core/contracts/EscrowFactory.sol index 81341b07bb..edebf52d97 100644 --- a/packages/core/contracts/EscrowFactory.sol +++ b/packages/core/contracts/EscrowFactory.sol @@ -20,6 +20,7 @@ contract EscrowFactory is OwnableUpgradeable, UUPSUpgradeable { address public staking; uint256 public minimumStake; address public admin; + address public kvstore; using SafeERC20 for IERC20; @@ -27,6 +28,7 @@ contract EscrowFactory is OwnableUpgradeable, UUPSUpgradeable { event LaunchedV2(address token, address escrow, string jobRequesterId); event SetStakingAddress(address indexed stakingAddress); event SetMinumumStake(uint256 indexed minimumStake); + event SetKVStoreAddress(address indexed kvStoreAddress); /// @custom:oz-upgrades-unsafe-allow constructor constructor() { @@ -35,10 +37,12 @@ contract EscrowFactory is OwnableUpgradeable, UUPSUpgradeable { function initialize( address _staking, - uint256 _minimumStake + uint256 _minimumStake, + address _kvstore ) external payable virtual initializer { __Ownable_init_unchained(); require(_staking != address(0), ERROR_ZERO_ADDRESS); + _setKVStoreAddress(_kvstore); _setStakingAddress(_staking); _setMinimumStake(_minimumStake); _setEscrowAdmin(msg.sender); @@ -58,7 +62,8 @@ contract EscrowFactory is OwnableUpgradeable, UUPSUpgradeable { _token, msg.sender, admin, - STANDARD_DURATION + STANDARD_DURATION, + kvstore ); counter++; escrowCounters[address(escrow)] = counter; @@ -92,9 +97,6 @@ contract EscrowFactory is OwnableUpgradeable, UUPSUpgradeable { * @param _reputationOracle Address of the reputation oracle. * @param _recordingOracle Address of the recording oracle. * @param _exchangeOracle Address of the exchange oracle. - * @param _reputationOracleFeePercentage Fee percentage for the reputation oracle. - * @param _recordingOracleFeePercentage Fee percentage for the recording oracle. - * @param _exchangeOracleFeePercentage Fee percentage for the exchange oracle. * @param _url URL for the escrow manifest. * @param _hash Hash of the escrow manifest. * @return The address of the newly created Escrow contract. @@ -106,9 +108,6 @@ contract EscrowFactory is OwnableUpgradeable, UUPSUpgradeable { address _reputationOracle, address _recordingOracle, address _exchangeOracle, - uint8 _reputationOracleFeePercentage, - uint8 _recordingOracleFeePercentage, - uint8 _exchangeOracleFeePercentage, string calldata _url, string calldata _hash ) external returns (address) { @@ -124,9 +123,6 @@ contract EscrowFactory is OwnableUpgradeable, UUPSUpgradeable { _reputationOracle, _recordingOracle, _exchangeOracle, - _reputationOracleFeePercentage, - _recordingOracleFeePercentage, - _exchangeOracleFeePercentage, _url, _hash ); @@ -179,6 +175,16 @@ contract EscrowFactory is OwnableUpgradeable, UUPSUpgradeable { admin = _admin; } + function setKVStoreAddress(address _kvstore) external onlyOwner { + _setKVStoreAddress(_kvstore); + } + + function _setKVStoreAddress(address _kvstore) private { + require(_kvstore != address(0), ERROR_ZERO_ADDRESS); + kvstore = _kvstore; + emit SetKVStoreAddress(_kvstore); + } + function _authorizeUpgrade(address) internal override onlyOwner {} /** @@ -186,5 +192,5 @@ contract EscrowFactory is OwnableUpgradeable, UUPSUpgradeable { * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[44] private __gap; + uint256[43] private __gap; } diff --git a/packages/core/contracts/interfaces/IEscrow.sol b/packages/core/contracts/interfaces/IEscrow.sol index 33d61c429c..c7cdbf9dcb 100644 --- a/packages/core/contracts/interfaces/IEscrow.sol +++ b/packages/core/contracts/interfaces/IEscrow.sol @@ -18,11 +18,8 @@ interface IEscrow { address _reputationOracle, address _recordingOracle, address _exchangeOracle, - uint8 _reputationOracleFeePercentage, - uint8 _recordingOracleFeePercentage, - uint8 _exchangeOracleFeePercentage, - string calldata _url, - string calldata _hash + string calldata _manifest, + string calldata _manifestHash ) external; function requestCancellation() external; diff --git a/packages/core/contracts/test/EscrowFactoryV0.sol b/packages/core/contracts/test/EscrowFactoryV0.sol index 4d474639b6..193a791c41 100644 --- a/packages/core/contracts/test/EscrowFactoryV0.sol +++ b/packages/core/contracts/test/EscrowFactoryV0.sol @@ -12,6 +12,7 @@ contract EscrowFactoryV0 is OwnableUpgradeable, UUPSUpgradeable { // all Escrows will have this duration. uint256 constant STANDARD_DURATION = 8640000; string constant ERROR_ZERO_ADDRESS = 'EscrowFactory: Zero Address'; + address constant DUMMY_KVSTORE = address(1); uint256 public counter; mapping(address => uint256) public escrowCounters; @@ -64,8 +65,9 @@ contract EscrowFactoryV0 is OwnableUpgradeable, UUPSUpgradeable { Escrow escrow = new Escrow( token, msg.sender, - payable(msg.sender), - STANDARD_DURATION + msg.sender, + STANDARD_DURATION, + DUMMY_KVSTORE ); counter++; escrowCounters[address(escrow)] = counter; diff --git a/packages/core/package.json b/packages/core/package.json index e9db16341a..908f7cdc6a 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -87,6 +87,7 @@ "ts-node": "^10.9.2", "typechain": "^8.3.2", "typescript": "^5.8.3", + "typescript-eslint": "^8.39.1", "xdeployer": "3.1.6" }, "peerDependencies": { diff --git a/packages/core/scripts/deploy-proxies.ts b/packages/core/scripts/deploy-proxies.ts index a5510394d2..08e6a8bb61 100644 --- a/packages/core/scripts/deploy-proxies.ts +++ b/packages/core/scripts/deploy-proxies.ts @@ -2,24 +2,24 @@ import { ethers, upgrades } from 'hardhat'; async function main() { - const hmtAddress = process.env.HMT_ADDRESS; - if (!hmtAddress) { - console.error('HMT_ADDRESS env variable missing'); - return; - } - const stakingAddress = process.env.STAKING_ADDRESS; if (!stakingAddress) { console.error('STAKING_ADDRESS env variable missing'); return; } + const kvStoreAddress = process.env.KVSTORE_ADDRESS; + if (!kvStoreAddress) { + console.error('KVSTORE_ADDRESS env variable missing'); + return; + } + const EscrowFactory = await ethers.getContractFactory( 'contracts/EscrowFactory.sol:EscrowFactory' ); const escrowFactoryContract = await upgrades.deployProxy( EscrowFactory, - [stakingAddress, 1], + [stakingAddress, 1, kvStoreAddress], { initializer: 'initialize', kind: 'uups' } ); await escrowFactoryContract.waitForDeployment(); @@ -33,11 +33,6 @@ async function main() { await escrowFactoryContract.getAddress() ) ); - - const KVStore = await ethers.getContractFactory('KVStore'); - const kvStoreContract = await KVStore.deploy(); - - console.log('KVStore Address: ', await kvStoreContract.getAddress()); } main().catch((error) => { diff --git a/packages/core/scripts/deploy.ts b/packages/core/scripts/deploy.ts index 7ef9c22a5c..3cfa410f35 100644 --- a/packages/core/scripts/deploy.ts +++ b/packages/core/scripts/deploy.ts @@ -23,12 +23,18 @@ async function main() { await stakingContract.waitForDeployment(); console.log('Staking Address: ', await stakingContract.getAddress()); + const KVStore = await ethers.getContractFactory('KVStore'); + const kvStoreContract = await KVStore.deploy(); + await kvStoreContract.waitForDeployment(); + const kvStoreAddress = await kvStoreContract.getAddress(); + console.log('KVStore Address: ', kvStoreAddress); + const EscrowFactory = await ethers.getContractFactory( 'contracts/EscrowFactory.sol:EscrowFactory' ); const escrowFactoryContract = await upgrades.deployProxy( EscrowFactory, - [await stakingContract.getAddress(), 1], + [await stakingContract.getAddress(), 1, kvStoreAddress], { initializer: 'initialize', kind: 'uups' } ); await escrowFactoryContract.waitForDeployment(); @@ -43,12 +49,6 @@ async function main() { ) ); - const KVStore = await ethers.getContractFactory('KVStore'); - const kvStoreContract = await KVStore.deploy(); - await kvStoreContract.waitForDeployment(); - - console.log('KVStore Address: ', await kvStoreContract.getAddress()); - for (const account of accounts) { await (HMTokenContract as HMToken).transfer( account.address, diff --git a/packages/core/test/Escrow.ts b/packages/core/test/Escrow.ts index 1a4cbd9eb2..ff6f2c3af5 100644 --- a/packages/core/test/Escrow.ts +++ b/packages/core/test/Escrow.ts @@ -1,7 +1,7 @@ import { expect } from 'chai'; import { ethers } from 'hardhat'; import { Signer, ZeroAddress } from 'ethers'; -import { Escrow, HMToken } from '../typechain-types'; +import { Escrow, HMToken, KVStore } from '../typechain-types'; import { faker } from '@faker-js/faker'; const BULK_MAX_COUNT = 100; @@ -41,8 +41,10 @@ let adminAddress: string; let token: HMToken; let token2: HMToken; let escrow: Escrow; +let kvStore: KVStore; let tokenAddress: string; let tokenAddress2: string; +let kvStoreAddress: string; async function deployEscrow( tokenAddr: string = tokenAddress, @@ -55,7 +57,8 @@ async function deployEscrow( tokenAddr, launcherAddr, adminAddr, - duration + duration, + kvStoreAddress )) as Escrow; } @@ -69,9 +72,6 @@ async function fundEscrow( } async function setupEscrow( - repFee = 3, - recFee = 3, - excFee = 3, url: string = FIXTURE_URL, hash: string = FIXTURE_HASH ) { @@ -81,9 +81,6 @@ async function setupEscrow( reputationOracleAddress, recordingOracleAddress, exchangeOracleAddress, - repFee, - recFee, - excFee, url, hash ); @@ -133,6 +130,14 @@ describe('Escrow', function () { tokenAddress = await token.getAddress(); token2 = (await HMToken.deploy(1000000000, 'Token2', 18, 'TK2')) as HMToken; tokenAddress2 = await token2.getAddress(); + + const KVStore = await ethers.getContractFactory('KVStore'); + kvStore = (await KVStore.deploy()) as KVStore; + kvStoreAddress = await kvStore.getAddress(); + + await kvStore.connect(reputationOracle).set('fee', '3'); + await kvStore.connect(recordingOracle).set('fee', '3'); + await kvStore.connect(exchangeOracle).set('fee', '3'); }); describe('deployment', () => { @@ -192,9 +197,6 @@ describe('Escrow', function () { reputationOracleAddress, recordingOracleAddress, exchangeOracleAddress, - 3, - 3, - 3, FIXTURE_URL, FIXTURE_HASH ) @@ -206,9 +208,6 @@ describe('Escrow', function () { reputationOracleAddress, recordingOracleAddress, exchangeOracleAddress, - 3, - 3, - 3, FIXTURE_URL, FIXTURE_HASH ) @@ -220,9 +219,6 @@ describe('Escrow', function () { reputationOracleAddress, recordingOracleAddress, exchangeOracleAddress, - 3, - 3, - 3, FIXTURE_URL, FIXTURE_HASH ) @@ -234,9 +230,6 @@ describe('Escrow', function () { reputationOracleAddress, recordingOracleAddress, exchangeOracleAddress, - 3, - 3, - 3, FIXTURE_URL, FIXTURE_HASH ) @@ -251,9 +244,6 @@ describe('Escrow', function () { ethers.ZeroAddress, recordingOracleAddress, exchangeOracleAddress, - 3, - 3, - 3, FIXTURE_URL, FIXTURE_HASH ) @@ -268,9 +258,6 @@ describe('Escrow', function () { reputationOracleAddress, ethers.ZeroAddress, exchangeOracleAddress, - 3, - 3, - 3, FIXTURE_URL, FIXTURE_HASH ) @@ -285,16 +272,16 @@ describe('Escrow', function () { reputationOracleAddress, recordingOracleAddress, ethers.ZeroAddress, - 3, - 3, - 3, FIXTURE_URL, FIXTURE_HASH ) ).to.be.revertedWith('Invalid exchange oracle'); }); - it('reverts when total fee > 100', async () => { + it('reverts when an oracle fee > 25', async () => { + await kvStore.connect(reputationOracle).set('fee', '50'); + await kvStore.connect(recordingOracle).set('fee', '20'); + await kvStore.connect(exchangeOracle).set('fee', '20'); await expect( escrow .connect(launcher) @@ -302,13 +289,13 @@ describe('Escrow', function () { reputationOracleAddress, recordingOracleAddress, exchangeOracleAddress, - 60, - 30, - 20, FIXTURE_URL, FIXTURE_HASH ) ).to.be.revertedWith('Percentage out of bounds'); + await kvStore.connect(reputationOracle).set('fee', '3'); + await kvStore.connect(recordingOracle).set('fee', '3'); + await kvStore.connect(exchangeOracle).set('fee', '3'); }); }); describe('succeeds', () => { @@ -321,26 +308,26 @@ describe('Escrow', function () { reputationOracleAddress, recordingOracleAddress, exchangeOracleAddress, - 5, - 5, - 5, FIXTURE_URL, FIXTURE_HASH ) ) - .to.emit(escrow, 'PendingV2') + .to.emit(escrow, 'PendingV3') .withArgs( FIXTURE_URL, FIXTURE_HASH, reputationOracleAddress, recordingOracleAddress, - exchangeOracleAddress + exchangeOracleAddress, + 3, + 3, + 3 ) .to.emit(escrow, 'Fund') .withArgs(amount); expect(await escrow.status()).to.equal(Status.Pending); - expect(await escrow.manifestUrl()).to.equal(FIXTURE_URL); + expect(await escrow.manifest()).to.equal(FIXTURE_URL); expect(await escrow.manifestHash()).to.equal(FIXTURE_HASH); }); @@ -353,26 +340,26 @@ describe('Escrow', function () { reputationOracleAddress, recordingOracleAddress, exchangeOracleAddress, - 5, - 5, - 5, FIXTURE_URL, FIXTURE_HASH ) ) - .to.emit(escrow, 'PendingV2') + .to.emit(escrow, 'PendingV3') .withArgs( FIXTURE_URL, FIXTURE_HASH, reputationOracleAddress, recordingOracleAddress, - exchangeOracleAddress + exchangeOracleAddress, + 3, + 3, + 3 ) .to.emit(escrow, 'Fund') .withArgs(amount); expect(await escrow.status()).to.equal(Status.Pending); - expect(await escrow.manifestUrl()).to.equal(FIXTURE_URL); + expect(await escrow.manifest()).to.equal(FIXTURE_URL); expect(await escrow.manifestHash()).to.equal(FIXTURE_HASH); }); }); diff --git a/packages/core/test/EscrowFactory.ts b/packages/core/test/EscrowFactory.ts index ab4941afb8..e7275670f8 100644 --- a/packages/core/test/EscrowFactory.ts +++ b/packages/core/test/EscrowFactory.ts @@ -2,7 +2,7 @@ import { anyValue } from '@nomicfoundation/hardhat-chai-matchers/withArgs'; import { expect } from 'chai'; import { EventLog, Signer, ZeroAddress } from 'ethers'; import { ethers, upgrades } from 'hardhat'; -import { EscrowFactory, HMToken, Staking } from '../typechain-types'; +import { EscrowFactory, HMToken, KVStore, Staking } from '../typechain-types'; import { faker } from '@faker-js/faker'; let owner: Signer, @@ -16,8 +16,11 @@ let exchangeOracleAddress: string, recordingOracleAddress: string, reputationOracleAddress: string; -let token: HMToken, escrowFactory: EscrowFactory, staking: Staking; -let stakingAddress: string, tokenAddress: string; +let token: HMToken, + escrowFactory: EscrowFactory, + staking: Staking, + kvStore: KVStore; +let stakingAddress: string, tokenAddress: string, kvStoreAddress: string; const MINIMUM_STAKE = ethers.parseEther('10'); const LOCK_PERIOD = 2; @@ -77,11 +80,19 @@ describe('EscrowFactory', function () { 'contracts/EscrowFactory.sol:EscrowFactory' ); + const KVStore = await ethers.getContractFactory('KVStore'); + kvStore = (await KVStore.deploy()) as KVStore; + kvStoreAddress = await kvStore.getAddress(); + escrowFactory = (await upgrades.deployProxy( EscrowFactory, - [await staking.getAddress(), MINIMUM_STAKE], + [await staking.getAddress(), MINIMUM_STAKE, kvStoreAddress], { kind: 'uups', initializer: 'initialize' } )) as unknown as EscrowFactory; + + await kvStore.connect(reputationOracle).set('fee', '5'); + await kvStore.connect(recordingOracle).set('fee', '5'); + await kvStore.connect(exchangeOracle).set('fee', '5'); }); describe('deployment', () => { @@ -92,10 +103,14 @@ describe('EscrowFactory', function () { ); await expect( - upgrades.deployProxy(EscrowFactory, [ZeroAddress, MINIMUM_STAKE], { - kind: 'uups', - initializer: 'initialize', - }) as unknown as EscrowFactory + upgrades.deployProxy( + EscrowFactory, + [ZeroAddress, MINIMUM_STAKE, kvStoreAddress], + { + kind: 'uups', + initializer: 'initialize', + } + ) as unknown as EscrowFactory ).revertedWith('Zero Address'); }); }); @@ -108,7 +123,7 @@ describe('EscrowFactory', function () { const escrowFactory = (await upgrades.deployProxy( EscrowFactory, - [stakingAddress, MINIMUM_STAKE], + [stakingAddress, MINIMUM_STAKE, kvStoreAddress], { kind: 'uups', initializer: 'initialize', @@ -252,7 +267,6 @@ describe('EscrowFactory', function () { }); describe('createFundAndSetupEscrow()', () => { - const fee = faker.number.int({ min: 1, max: 5 }); const manifestUrl = faker.internet.url(); const manifestHash = faker.string.alphanumeric(46); const fundAmount = ethers.parseEther( @@ -270,9 +284,6 @@ describe('EscrowFactory', function () { reputationOracleAddress, recordingOracleAddress, exchangeOracleAddress, - fee, - fee, - fee, manifestUrl, manifestHash ) @@ -290,9 +301,6 @@ describe('EscrowFactory', function () { reputationOracleAddress, recordingOracleAddress, exchangeOracleAddress, - fee, - fee, - fee, manifestUrl, manifestHash ) @@ -315,9 +323,6 @@ describe('EscrowFactory', function () { reputationOracleAddress, recordingOracleAddress, exchangeOracleAddress, - fee, - fee, - fee, manifestUrl, manifestHash ) @@ -342,9 +347,6 @@ describe('EscrowFactory', function () { reputationOracleAddress, recordingOracleAddress, exchangeOracleAddress, - fee, - fee, - fee, manifestUrl, manifestHash ); @@ -367,13 +369,16 @@ describe('EscrowFactory', function () { await expect(tx) .to.emit(escrowFactory, 'LaunchedV2') .withArgs(tokenAddress, escrowAddress, FIXTURE_REQUESTER_ID) - .to.emit(escrow, 'PendingV2') + .to.emit(escrow, 'PendingV3') .withArgs( manifestUrl, manifestHash, reputationOracleAddress, recordingOracleAddress, - exchangeOracleAddress + exchangeOracleAddress, + 5, + 5, + 5 ) .to.emit(escrow, 'Fund') .withArgs(fundAmount); diff --git a/packages/core/test/Staking.ts b/packages/core/test/Staking.ts index a012a69d7d..18ea049ffc 100644 --- a/packages/core/test/Staking.ts +++ b/packages/core/test/Staking.ts @@ -2,7 +2,7 @@ import { anyValue } from '@nomicfoundation/hardhat-chai-matchers/withArgs'; import { expect } from 'chai'; import { ethers, upgrades } from 'hardhat'; import { EventLog, Signer } from 'ethers'; -import { EscrowFactory, HMToken, Staking } from '../typechain-types'; +import { EscrowFactory, HMToken, KVStore, Staking } from '../typechain-types'; const mineNBlocks = async (n: number) => { await Promise.all( @@ -30,6 +30,7 @@ describe('Staking', function () { recordingOracle: Signer; let token: HMToken, escrowFactory: EscrowFactory, staking: Staking; + let kvStore: KVStore; this.beforeAll(async () => { [ @@ -91,10 +92,12 @@ describe('Staking', function () { const EscrowFactory = await ethers.getContractFactory( 'contracts/EscrowFactory.sol:EscrowFactory' ); + const KVStore = await ethers.getContractFactory('KVStore'); + kvStore = (await KVStore.deploy()) as KVStore; escrowFactory = (await upgrades.deployProxy( EscrowFactory, - [await staking.getAddress(), minimumStake], + [await staking.getAddress(), minimumStake, await kvStore.getAddress()], { kind: 'uups', initializer: 'initialize' } )) as unknown as EscrowFactory; diff --git a/packages/sdk/python/human-protocol-sdk/README.md b/packages/sdk/python/human-protocol-sdk/README.md index ecd654d90d..141ed461fa 100644 --- a/packages/sdk/python/human-protocol-sdk/README.md +++ b/packages/sdk/python/human-protocol-sdk/README.md @@ -129,10 +129,7 @@ escrow_config = EscrowConfig( recording_oracle_address = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", reputation_oracle_address = "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC", exchange_oracle_address = "0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", - recording_oracle_fee = 10, - reputation_oracle_fee = 10, - exchange_oracle_fee = 10, - manifest_url = "http://localhost:9000/manifests/manifest.json", + manifest = "http://localhost:9000/manifests/manifest.json", hash = "s3ca3basd132bafcdas234243.json" ) ``` diff --git a/packages/sdk/python/human-protocol-sdk/docs/index.md b/packages/sdk/python/human-protocol-sdk/docs/index.md index 54585e09c3..6ee7e810c2 100644 --- a/packages/sdk/python/human-protocol-sdk/docs/index.md +++ b/packages/sdk/python/human-protocol-sdk/docs/index.md @@ -117,9 +117,6 @@ config = EscrowConfig( recording_oracle_address="0x...", reputation_oracle_address="0x...", exchange_oracle_address="0x...", - recording_oracle_fee=10, - reputation_oracle_fee=10, - exchange_oracle_fee=10, manifest="https://example.com/manifest.json", hash="manifest_hash", ) diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/constants.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/constants.py index a059cc86d4..967c0cb969 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/constants.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/constants.py @@ -143,6 +143,12 @@ class OperatorCategory(Enum): "subgraph_url_api_key": ( "https://gateway.thegraph.com/api/deployments/id/QmcLwLMw3UzCSbNbjegrpNu6PB3kAd67xquuyaVWvc5Q7Q" ), + "hmt_subgraph_url": ( + "https://api.studio.thegraph.com/query/74256/hmt-stats-amoy/version/latest" + ), + "hmt_subgraph_url_api_key": ( + "https://gateway.thegraph.com/api/deployments/id/QmZJYBQKyrsEJHQwNQ8JhkKcB5G8zQdrcwnr7ecGRNTBit" + ), "hmt_address": "0x792abbcC99c01dbDec49c9fa9A828a186Da45C33", "factory_address": "0xAFf5a986A530ff839d49325A5dF69F96627E8D29", "staking_address": "0xffE496683F842a923110415b7278ded3F265f2C5", diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/escrow/escrow_client.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/escrow/escrow_client.py index 188816998e..c40277fb95 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/escrow/escrow_client.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/escrow/escrow_client.py @@ -111,9 +111,6 @@ class EscrowConfig: recording_oracle_address (str): Address of the recording oracle. reputation_oracle_address (str): Address of the reputation oracle. exchange_oracle_address (str): Address of the exchange oracle. - recording_oracle_fee (int): Recording oracle fee percentage (0-100). - reputation_oracle_fee (int): Reputation oracle fee percentage (0-100). - exchange_oracle_fee (int): Exchange oracle fee percentage (0-100). manifest (str): Manifest payload (URL or JSON string). hash (str): Manifest file hash. """ @@ -123,16 +120,12 @@ def __init__( recording_oracle_address: str, reputation_oracle_address: str, exchange_oracle_address: str, - recording_oracle_fee: int, - reputation_oracle_fee: int, - exchange_oracle_fee: int, - manifest: str, - hash: str, + manifest: str = "", + hash: str = "", ): """ Raises: - EscrowClientError: If addresses are invalid, fees are out of range, - total fees exceed 100%, or manifest data is invalid. + EscrowClientError: If addresses are invalid or manifest data is invalid. """ if not Web3.is_address(recording_oracle_address): raise EscrowClientError( @@ -147,14 +140,6 @@ def __init__( f"Invalid exchange oracle address: {exchange_oracle_address}" ) - if ( - not (0 <= recording_oracle_fee <= 100) - or not (0 <= reputation_oracle_fee <= 100) - or not (0 <= exchange_oracle_fee <= 100) - ): - raise EscrowClientError("Fee must be between 0 and 100") - if recording_oracle_fee + reputation_oracle_fee + exchange_oracle_fee > 100: - raise EscrowClientError("Total fee must be less than 100") if not validate_url(manifest) and not validate_json(manifest): raise EscrowClientError("Invalid empty manifest") if not hash: @@ -163,9 +148,6 @@ def __init__( self.recording_oracle_address = recording_oracle_address self.reputation_oracle_address = reputation_oracle_address self.exchange_oracle_address = exchange_oracle_address - self.recording_oracle_fee = recording_oracle_fee - self.reputation_oracle_fee = reputation_oracle_fee - self.exchange_oracle_fee = exchange_oracle_fee self.manifest = manifest self.hash = hash @@ -289,7 +271,7 @@ def create_fund_and_setup_escrow( amount (int): Token amount to fund (in token's smallest unit). job_requester_id (str): Off-chain identifier for the job requester. escrow_config (EscrowConfig): Escrow configuration parameters including - oracle addresses, fees, and manifest data. + oracle addresses and manifest data. tx_options (Optional[TransactionOptions]): Optional transaction parameters such as gas limit. Returns: @@ -321,9 +303,6 @@ def create_fund_and_setup_escrow( escrow_config.reputation_oracle_address, escrow_config.recording_oracle_address, escrow_config.exchange_oracle_address, - escrow_config.reputation_oracle_fee, - escrow_config.recording_oracle_fee, - escrow_config.exchange_oracle_fee, escrow_config.manifest, escrow_config.hash, ), @@ -349,14 +328,14 @@ def setup( escrow_config: EscrowConfig, tx_options: Optional[TransactionOptions] = None, ) -> None: - """Set escrow roles, fees, and manifest metadata. + """Set escrow roles and manifest metadata. - Configures the escrow with oracle addresses, fee percentages, and manifest information. + Configures the escrow with oracle addresses and manifest information. Args: escrow_address (str): Address of the escrow contract to configure. escrow_config (EscrowConfig): Escrow configuration parameters including - oracle addresses, fees, and manifest data. + oracle addresses and manifest data. tx_options (Optional[TransactionOptions]): Optional transaction parameters such as gas limit. Returns: @@ -380,9 +359,6 @@ def setup( escrow_config.reputation_oracle_address, escrow_config.recording_oracle_address, escrow_config.exchange_oracle_address, - escrow_config.reputation_oracle_fee, - escrow_config.recording_oracle_fee, - escrow_config.exchange_oracle_fee, escrow_config.manifest, escrow_config.hash, ), @@ -1031,7 +1007,13 @@ def get_manifest(self, escrow_address: str) -> str: if not Web3.is_address(escrow_address): raise EscrowClientError(f"Invalid escrow address: {escrow_address}") - return self._get_escrow_contract(escrow_address).functions.manifestUrl().call() + escrow_contract = self._get_escrow_contract(escrow_address) + + try: + return escrow_contract.functions.manifest().call() + except Exception: + # Backward compatibility with legacy escrows exposing manifestUrl(). + return escrow_contract.functions.manifestUrl().call() def get_results_url(self, escrow_address: str) -> str: """Get the final results file URL. diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/cancel.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/cancel.py index 6d5ddf36db..223b88aafb 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/cancel.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/cancel.py @@ -70,6 +70,4 @@ def get_cancellation_refund_by_escrow_query(): }} }} {cancellation_refund_fragment} -""".format( - cancellation_refund_fragment=cancellation_refund_fragment - ) +""".format(cancellation_refund_fragment=cancellation_refund_fragment) diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/escrow.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/escrow.py index 38a0ca8919..5d1b01f6e6 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/escrow.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/escrow.py @@ -97,9 +97,7 @@ def get_escrow_query(): }} }} {escrow_fragment} -""".format( - escrow_fragment=escrow_fragment - ) +""".format(escrow_fragment=escrow_fragment) def get_status_query( diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/reward.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/reward.py index 3f43bbff9e..09c06278ae 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/reward.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/reward.py @@ -14,6 +14,4 @@ }} }} {reward_added_event_fragment} -""".format( - reward_added_event_fragment=reward_added_event_fragment -) +""".format(reward_added_event_fragment=reward_added_event_fragment) diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/statistics.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/statistics.py index e726402117..988914d436 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/statistics.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/statistics.py @@ -41,7 +41,12 @@ dailyEscrowCount dailyWorkerCount dailyPayoutCount - dailyHMTPayoutAmount +} +""" + +hmt_event_day_data_fragment = """ +fragment HMTEventDayDataFields on EventDayData { + timestamp dailyHMTTransferCount dailyHMTTransferAmount dailyUniqueSenders @@ -100,3 +105,33 @@ def get_event_day_data_query(filter: StatisticsFilter) -> str: from_clause="timestamp_gte: $from" if filter.date_from else "", to_clause="timestamp_lte: $to" if filter.date_to else "", ) + + +def get_hmt_event_day_data_query(filter: StatisticsFilter) -> str: + return """ +query GetHMTDayData( + $from: Int + $to: Int + $orderDirection: String + $first: Int + $skip: Int +) {{ + eventDayDatas( + where: {{ + {from_clause} + {to_clause} + }}, + orderBy: timestamp, + orderDirection: $orderDirection + first: $first + skip: $skip + ) {{ + ...HMTEventDayDataFields + }} +}} +{hmt_event_day_data_fragment} +""".format( + hmt_event_day_data_fragment=hmt_event_day_data_fragment, + from_clause="timestamp_gte: $from" if filter.date_from else "", + to_clause="timestamp_lte: $to" if filter.date_to else "", + ) diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/transaction.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/transaction.py index ef637424b2..f17cc61876 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/transaction.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/transaction.py @@ -103,6 +103,4 @@ def get_transaction_query() -> str: }} }} {transaction_fragment} -""".format( - transaction_fragment=transaction_fragment - ) +""".format(transaction_fragment=transaction_fragment) diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/worker.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/worker.py index 9788272612..ee21af1ef7 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/worker.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/gql/worker.py @@ -18,9 +18,7 @@ def get_worker_query() -> str: }} }} {worker_fragment} -""".format( - worker_fragment=worker_fragment - ) +""".format(worker_fragment=worker_fragment) def get_workers_query(filter: WorkerFilter) -> str: diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/statistics/statistics_utils.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/statistics/statistics_utils.py index 5d79cb567a..32c1c0ab04 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/statistics/statistics_utils.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/statistics/statistics_utils.py @@ -127,22 +127,16 @@ class DailyPaymentData: Attributes: timestamp (datetime): Day boundary timestamp. - total_amount_paid (int): Total amount paid out on this day. total_count (int): Number of payment transactions. - average_amount_per_worker (int): Average payout amount per worker. """ def __init__( self, timestamp: datetime, - total_amount_paid: int, total_count: int, - average_amount_per_worker: int, ): self.timestamp = timestamp - self.total_amount_paid = total_amount_paid self.total_count = total_count - self.average_amount_per_worker = average_amount_per_worker class PaymentStatistics: @@ -446,8 +440,7 @@ def get_payment_statistics( ) ) for day_data in stats.daily_payments_data: - print(f"{day_data.timestamp}: {day_data.total_amount_paid} paid") - print(f" Average per worker: {day_data.average_amount_per_worker}") + print(f"{day_data.total_count}: {day_data.total_count} payments") ``` """ if chain_id.value not in [cid.value for cid in ChainId]: @@ -481,16 +474,7 @@ def get_payment_statistics( timestamp=datetime.fromtimestamp( int(event_day_data.get("timestamp", 0)) ), - total_amount_paid=int( - event_day_data.get("dailyHMTPayoutAmount", 0) - ), total_count=int(event_day_data.get("dailyPayoutCount", 0)), - average_amount_per_worker=( - int(event_day_data.get("dailyHMTPayoutAmount", 0)) - / int(event_day_data.get("dailyWorkerCount")) - if event_day_data.get("dailyWorkerCount", "0") != "0" - else 0 - ), ) for event_day_data in event_day_datas ], @@ -540,6 +524,7 @@ def get_hmt_statistics( network, query=get_hmtoken_statistics_query, options=options, + use_hmt_subgraph=True, ) hmtoken_statistics = hmtoken_statistics_data["data"]["hmtokenStatistics"] @@ -612,6 +597,7 @@ def get_hmt_holders( "orderDirection": param.order_direction, }, options=options, + use_hmt_subgraph=True, ) holders = holders_data["data"]["holders"] @@ -675,12 +661,12 @@ def get_hmt_daily_data( raise StatisticsUtilsError("Empty network configuration") from human_protocol_sdk.gql.statistics import ( - get_event_day_data_query, + get_hmt_event_day_data_query, ) event_day_datas_data = custom_gql_fetch( network, - query=get_event_day_data_query(filter), + query=get_hmt_event_day_data_query(filter), params={ "from": int(filter.date_from.timestamp()) if filter.date_from else None, "to": int(filter.date_to.timestamp()) if filter.date_to else None, @@ -689,6 +675,7 @@ def get_hmt_daily_data( "orderDirection": filter.order_direction.value, }, options=options, + use_hmt_subgraph=True, ) event_day_datas = event_day_datas_data["data"]["eventDayDatas"] diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py index a8f7281d92..0f16e73606 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py @@ -207,11 +207,30 @@ def is_indexer_error(error: Exception) -> bool: return "bad indexers" in message.lower() +def _resolve_subgraph_urls( + network: Dict[str, Any], use_hmt_subgraph: bool = False +) -> Tuple[str, str]: + """Resolve public/API-key URLs for either default or HMT subgraph.""" + if not use_hmt_subgraph: + return network["subgraph_url"], network["subgraph_url_api_key"] + + hmt_subgraph_url = network.get("hmt_subgraph_url") + hmt_subgraph_url_api_key = network.get("hmt_subgraph_url_api_key") + + public_url = hmt_subgraph_url or network["subgraph_url"] + api_key_url = ( + hmt_subgraph_url_api_key or hmt_subgraph_url or network["subgraph_url_api_key"] + ) + + return public_url, api_key_url + + def custom_gql_fetch( network: Dict[str, Any], query: str, params: Optional[Dict[str, Any]] = None, options: Optional[SubgraphOptions] = None, + use_hmt_subgraph: bool = False, ) -> Dict[str, Any]: """Fetch data from the subgraph with optional retry logic and indexer routing. @@ -220,6 +239,7 @@ def custom_gql_fetch( query (str): GraphQL query string to execute. params (Optional[Dict[str, Any]]): Optional query parameters/variables dictionary. options (Optional[SubgraphOptions]): Optional subgraph configuration for retries and indexer selection. + use_hmt_subgraph (bool): If true, resolves URL using HMT subgraph keys with fallback. Returns: JSON response from the subgraph containing the query results. @@ -250,7 +270,12 @@ def custom_gql_fetch( subgraph_api_key = os.getenv("SUBGRAPH_API_KEY", "") if not options: - return _fetch_subgraph_data(network, query, params) + return _fetch_subgraph_data( + network, + query, + params, + use_hmt_subgraph=use_hmt_subgraph, + ) has_max_retries = options.max_retries is not None has_base_delay = options.base_delay is not None @@ -272,7 +297,13 @@ def custom_gql_fetch( for attempt in range(max_retries + 1): try: - return _fetch_subgraph_data(network, query, params, options.indexer_id) + return _fetch_subgraph_data( + network, + query, + params, + options.indexer_id, + use_hmt_subgraph, + ) except Exception as error: last_error = error @@ -290,6 +321,7 @@ def _fetch_subgraph_data( query: str, params: Optional[Dict[str, Any]] = None, indexer_id: Optional[str] = None, + use_hmt_subgraph: bool = False, ) -> Dict[str, Any]: """Internal function to fetch data from the subgraph API. @@ -298,6 +330,7 @@ def _fetch_subgraph_data( query (str): GraphQL query string to execute. params (Optional[Dict[str, Any]]): Optional query parameters/variables dictionary. indexer_id (Optional[str]): Optional indexer ID to route the request to. + use_hmt_subgraph (bool): If true, resolves URL using HMT subgraph keys with fallback. Returns: JSON response from the subgraph. @@ -305,14 +338,17 @@ def _fetch_subgraph_data( Raises: Exception: If the HTTP request fails or returns a non-200 status code. """ + default_subgraph_url, default_subgraph_url_api_key = _resolve_subgraph_urls( + network, use_hmt_subgraph + ) subgraph_api_key = os.getenv("SUBGRAPH_API_KEY", "") if subgraph_api_key: - subgraph_url = network["subgraph_url_api_key"] + subgraph_url = default_subgraph_url_api_key else: logger.warning( "Warning: SUBGRAPH_API_KEY is not provided. It might cause issues with the subgraph." ) - subgraph_url = network["subgraph_url"] + subgraph_url = default_subgraph_url subgraph_url = _attach_indexer_id(subgraph_url, indexer_id) diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_client.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_client.py index 6b1fc49cb5..3c4c8c22b9 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_client.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_client.py @@ -115,9 +115,6 @@ def test_escrow_config_valid_params(self): recording_oracle_address = "0x1234567890123456789012345678901234567890" reputation_oracle_address = "0x1234567890123456789012345678901234567890" exchange_oracle_address = "0x1234567890123456789012345678901234567890" - recording_oracle_fee = 10 - reputation_oracle_fee = 10 - exchange_oracle_fee = 10 manifest = "https://www.example.com/result" hash = "test" @@ -125,9 +122,6 @@ def test_escrow_config_valid_params(self): recording_oracle_address, reputation_oracle_address, exchange_oracle_address, - recording_oracle_fee, - reputation_oracle_fee, - exchange_oracle_fee, manifest, hash, ) @@ -139,9 +133,6 @@ def test_escrow_config_valid_params(self): escrow_config.reputation_oracle_address, reputation_oracle_address ) self.assertEqual(escrow_config.exchange_oracle_address, exchange_oracle_address) - self.assertEqual(escrow_config.recording_oracle_fee, recording_oracle_fee) - self.assertEqual(escrow_config.reputation_oracle_fee, reputation_oracle_fee) - self.assertEqual(escrow_config.exchange_oracle_fee, exchange_oracle_fee) self.assertEqual(escrow_config.manifest, manifest) self.assertEqual(escrow_config.hash, hash) @@ -149,9 +140,6 @@ def test_escrow_config_valid_params_with_json_manifest(self): recording_oracle_address = "0x1234567890123456789012345678901234567890" reputation_oracle_address = "0x1234567890123456789012345678901234567890" exchange_oracle_address = "0x1234567890123456789012345678901234567890" - recording_oracle_fee = 10 - reputation_oracle_fee = 10 - exchange_oracle_fee = 10 manifest = '{"foo": "bar"}' hash = "test" @@ -159,9 +147,6 @@ def test_escrow_config_valid_params_with_json_manifest(self): recording_oracle_address, reputation_oracle_address, exchange_oracle_address, - recording_oracle_fee, - reputation_oracle_fee, - exchange_oracle_fee, manifest, hash, ) @@ -171,9 +156,6 @@ def test_escrow_config_valid_params_with_docker_network_url(self): recording_oracle_address = "0x1234567890123456789012345678901234567890" reputation_oracle_address = "0x1234567890123456789012345678901234567890" exchange_oracle_address = "0x1234567890123456789012345678901234567890" - recording_oracle_fee = 10 - reputation_oracle_fee = 10 - exchange_oracle_fee = 10 manifest = "http://test:6000" hash = "test" @@ -181,9 +163,6 @@ def test_escrow_config_valid_params_with_docker_network_url(self): recording_oracle_address, reputation_oracle_address, exchange_oracle_address, - recording_oracle_fee, - reputation_oracle_fee, - exchange_oracle_fee, manifest, hash, ) @@ -195,18 +174,12 @@ def test_escrow_config_valid_params_with_docker_network_url(self): escrow_config.reputation_oracle_address, reputation_oracle_address ) self.assertEqual(escrow_config.exchange_oracle_address, exchange_oracle_address) - self.assertEqual(escrow_config.recording_oracle_fee, recording_oracle_fee) - self.assertEqual(escrow_config.reputation_oracle_fee, reputation_oracle_fee) - self.assertEqual(escrow_config.exchange_oracle_fee, exchange_oracle_fee) self.assertEqual(escrow_config.manifest, manifest) self.assertEqual(escrow_config.hash, hash) def test_escrow_config_invalid_address(self): invalid_address = "invalid_address" valid_address = "0x1234567890123456789012345678901234567890" - recording_oracle_fee = 10 - reputation_oracle_fee = 10 - exchange_oracle_fee = 10 manifest_url = "https://www.example.com/result" hash = "test" @@ -215,9 +188,6 @@ def test_escrow_config_invalid_address(self): invalid_address, valid_address, valid_address, - recording_oracle_fee, - reputation_oracle_fee, - exchange_oracle_fee, manifest_url, hash, ) @@ -230,9 +200,6 @@ def test_escrow_config_invalid_address(self): valid_address, invalid_address, valid_address, - recording_oracle_fee, - reputation_oracle_fee, - exchange_oracle_fee, manifest_url, hash, ) @@ -245,9 +212,6 @@ def test_escrow_config_invalid_address(self): valid_address, valid_address, invalid_address, - recording_oracle_fee, - reputation_oracle_fee, - exchange_oracle_fee, manifest_url, hash, ) @@ -255,112 +219,10 @@ def test_escrow_config_invalid_address(self): f"Invalid exchange oracle address: {invalid_address}", str(cm.exception) ) - def test_escrow_config_invalid_fee(self): - recording_oracle_address = "0x1234567890123456789012345678901234567890" - reputation_oracle_address = "0x1234567890123456789012345678901234567890" - exchange_oracle_address = "0x1234567890123456789012345678901234567890" - valid_oracle_fee = 10 - manifest_url = "https://www.example.com/result" - hash = "test" - - with self.assertRaises(EscrowClientError) as cm: - EscrowConfig( - recording_oracle_address, - reputation_oracle_address, - exchange_oracle_address, - (-2), - valid_oracle_fee, - valid_oracle_fee, - manifest_url, - hash, - ) - self.assertEqual("Fee must be between 0 and 100", str(cm.exception)) - - with self.assertRaises(EscrowClientError) as cm: - EscrowConfig( - recording_oracle_address, - reputation_oracle_address, - exchange_oracle_address, - 1000, - valid_oracle_fee, - valid_oracle_fee, - manifest_url, - hash, - ) - self.assertEqual("Fee must be between 0 and 100", str(cm.exception)) - - with self.assertRaises(EscrowClientError) as cm: - EscrowConfig( - recording_oracle_address, - reputation_oracle_address, - exchange_oracle_address, - valid_oracle_fee, - (-2), - valid_oracle_fee, - manifest_url, - hash, - ) - self.assertEqual("Fee must be between 0 and 100", str(cm.exception)) - - with self.assertRaises(EscrowClientError) as cm: - EscrowConfig( - recording_oracle_address, - reputation_oracle_address, - exchange_oracle_address, - valid_oracle_fee, - 1000, - valid_oracle_fee, - manifest_url, - hash, - ) - self.assertEqual("Fee must be between 0 and 100", str(cm.exception)) - - with self.assertRaises(EscrowClientError) as cm: - EscrowConfig( - recording_oracle_address, - reputation_oracle_address, - exchange_oracle_address, - valid_oracle_fee, - valid_oracle_fee, - (-2), - manifest_url, - hash, - ) - self.assertEqual("Fee must be between 0 and 100", str(cm.exception)) - - with self.assertRaises(EscrowClientError) as cm: - EscrowConfig( - recording_oracle_address, - reputation_oracle_address, - exchange_oracle_address, - valid_oracle_fee, - valid_oracle_fee, - 1000, - manifest_url, - hash, - ) - self.assertEqual("Fee must be between 0 and 100", str(cm.exception)) - - with self.assertRaises(EscrowClientError) as cm: - EscrowConfig( - recording_oracle_address, - reputation_oracle_address, - exchange_oracle_address, - 40, - 40, - 40, - manifest_url, - hash, - ) - self.assertEqual("Total fee must be less than 100", str(cm.exception)) - def test_escrow_config_invalid_manifest(self): recording_oracle_address = "0x1234567890123456789012345678901234567890" reputation_oracle_address = "0x1234567890123456789012345678901234567890" exchange_oracle_address = "0x1234567890123456789012345678901234567890" - recording_oracle_fee = 10 - reputation_oracle_fee = 10 - exchange_oracle_fee = 10 manifest = "" hash = "test" @@ -369,9 +231,6 @@ def test_escrow_config_invalid_manifest(self): recording_oracle_address, reputation_oracle_address, exchange_oracle_address, - recording_oracle_fee, - reputation_oracle_fee, - exchange_oracle_fee, manifest, hash, ) @@ -381,9 +240,6 @@ def test_escrow_config_invalid_hash(self): recording_oracle_address = "0x1234567890123456789012345678901234567890" reputation_oracle_address = "0x1234567890123456789012345678901234567890" exchange_oracle_address = "0x1234567890123456789012345678901234567890" - recording_oracle_fee = 10 - reputation_oracle_fee = 10 - exchange_oracle_fee = 10 manifest_url = "https://www.example.com/result" hash = "" @@ -392,9 +248,6 @@ def test_escrow_config_invalid_hash(self): recording_oracle_address, reputation_oracle_address, exchange_oracle_address, - recording_oracle_fee, - reputation_oracle_fee, - exchange_oracle_fee, manifest_url, hash, ) @@ -559,9 +412,6 @@ def test_create_fund_and_setup_escrow(self): "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", - 10, - 10, - 10, "https://www.example.com/manifest", "hashvalue", ) @@ -577,9 +427,6 @@ def test_create_fund_and_setup_escrow(self): escrow_config.reputation_oracle_address, escrow_config.recording_oracle_address, escrow_config.exchange_oracle_address, - escrow_config.reputation_oracle_fee, - escrow_config.recording_oracle_fee, - escrow_config.exchange_oracle_fee, escrow_config.manifest, escrow_config.hash, ) @@ -599,9 +446,6 @@ def test_create_fund_and_setup_escrow_invalid_token(self): "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", - 10, - 10, - 10, "https://www.example.com/manifest", "hashvalue", ) @@ -627,9 +471,6 @@ def test_create_fund_and_setup_escrow_without_account(self): "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", - 10, - 10, - 10, "https://www.example.com/manifest", "hashvalue", ) @@ -667,9 +508,6 @@ def test_create_fund_and_setup_escrow_with_tx_options(self): "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", - 10, - 10, - 10, "https://www.example.com/manifest", "hashvalue", ) @@ -685,9 +523,6 @@ def test_create_fund_and_setup_escrow_with_tx_options(self): escrow_config.reputation_oracle_address, escrow_config.recording_oracle_address, escrow_config.exchange_oracle_address, - escrow_config.reputation_oracle_fee, - escrow_config.recording_oracle_fee, - escrow_config.exchange_oracle_fee, escrow_config.manifest, escrow_config.hash, ) @@ -726,9 +561,6 @@ def test_create_fund_and_setup_escrow_with_wait_options(self): "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", - 10, - 10, - 10, "https://www.example.com/manifest", "hashvalue", ) @@ -744,9 +576,6 @@ def test_create_fund_and_setup_escrow_with_wait_options(self): escrow_config.reputation_oracle_address, escrow_config.recording_oracle_address, escrow_config.exchange_oracle_address, - escrow_config.reputation_oracle_fee, - escrow_config.recording_oracle_fee, - escrow_config.exchange_oracle_fee, escrow_config.manifest, escrow_config.hash, ) @@ -774,9 +603,6 @@ def test_setup(self): "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", - 10, - 10, - 10, "https://www.example.com/result", "test", ) @@ -788,9 +614,6 @@ def test_setup(self): escrow_config.reputation_oracle_address, escrow_config.recording_oracle_address, escrow_config.exchange_oracle_address, - escrow_config.reputation_oracle_fee, - escrow_config.recording_oracle_fee, - escrow_config.exchange_oracle_fee, escrow_config.manifest, escrow_config.hash, ) @@ -806,9 +629,6 @@ def test_setup_invalid_address(self): "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", - 10, - 10, - 10, "https://www.example.com/result", "test", ) @@ -833,9 +653,6 @@ def test_get_manifest(self): "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", - 10, - 10, - 10, "https://www.example.com/result", "test", ) @@ -856,9 +673,6 @@ def test_setup_without_account(self): "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", - 10, - 10, - 10, "https://www.example.com/result", "test", ) @@ -878,9 +692,6 @@ def test_setup_invalid_status(self): "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", - 10, - 10, - 10, "https://www.example.com/result", "test", ) @@ -904,9 +715,6 @@ def test_setup_invalid_caller(self): "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", - 10, - 10, - 10, "https://www.example.com/result", "test", ) @@ -931,9 +739,6 @@ def test_setup_with_tx_options(self): "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", - 10, - 10, - 10, "https://www.example.com/result", "test", ) @@ -946,9 +751,6 @@ def test_setup_with_tx_options(self): escrow_config.reputation_oracle_address, escrow_config.recording_oracle_address, escrow_config.exchange_oracle_address, - escrow_config.reputation_oracle_fee, - escrow_config.recording_oracle_fee, - escrow_config.exchange_oracle_fee, escrow_config.manifest, escrow_config.hash, ) @@ -972,9 +774,6 @@ def test_setup_with_wait_options(self): "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", "0x1234567890123456789012345678901234567890", - 10, - 10, - 10, "https://www.example.com/result", "test", ) @@ -987,9 +786,6 @@ def test_setup_with_wait_options(self): escrow_config.reputation_oracle_address, escrow_config.recording_oracle_address, escrow_config.exchange_oracle_address, - escrow_config.reputation_oracle_fee, - escrow_config.recording_oracle_fee, - escrow_config.exchange_oracle_fee, escrow_config.manifest, escrow_config.hash, ) @@ -2493,9 +2289,26 @@ def test_get_manifest_hash_invalid_address(self): def test_get_manifest(self): mock_contract = MagicMock() + mock_contract.functions.manifest = MagicMock() + mock_contract.functions.manifest.return_value.call.return_value = "mock_value" + self.escrow._get_escrow_contract = MagicMock(return_value=mock_contract) + escrow_address = "0x1234567890123456789012345678901234567890" + + result = self.escrow.get_manifest(escrow_address) + + self.escrow._get_escrow_contract.assert_called_once_with(escrow_address) + mock_contract.functions.manifest.assert_called_once_with() + self.assertEqual(result, "mock_value") + + def test_get_manifest_fallback_to_manifest_url(self): + mock_contract = MagicMock() + mock_contract.functions.manifest = MagicMock() + mock_contract.functions.manifest.return_value.call.side_effect = Exception( + "manifest() not found" + ) mock_contract.functions.manifestUrl = MagicMock() mock_contract.functions.manifestUrl.return_value.call.return_value = ( - "mock_value" + "legacy_manifest" ) self.escrow._get_escrow_contract = MagicMock(return_value=mock_contract) escrow_address = "0x1234567890123456789012345678901234567890" @@ -2503,8 +2316,9 @@ def test_get_manifest(self): result = self.escrow.get_manifest(escrow_address) self.escrow._get_escrow_contract.assert_called_once_with(escrow_address) + mock_contract.functions.manifest.assert_called_once_with() mock_contract.functions.manifestUrl.assert_called_once_with() - self.assertEqual(result, "mock_value") + self.assertEqual(result, "legacy_manifest") def test_get_manifest_invalid_address(self): with self.assertRaises(EscrowClientError) as cm: @@ -2519,17 +2333,15 @@ def test_get_manifest_without_account(self): escrowClient = EscrowClient(w3) mock_contract = MagicMock() - mock_contract.functions.manifestUrl = MagicMock() - mock_contract.functions.manifestUrl.return_value.call.return_value = ( - "mock_value" - ) + mock_contract.functions.manifest = MagicMock() + mock_contract.functions.manifest.return_value.call.return_value = "mock_value" escrowClient._get_escrow_contract = MagicMock(return_value=mock_contract) escrow_address = "0x1234567890123456789012345678901234567890" result = escrowClient.get_manifest(escrow_address) escrowClient._get_escrow_contract.assert_called_once_with(escrow_address) - mock_contract.functions.manifestUrl.assert_called_once_with() + mock_contract.functions.manifest.assert_called_once_with() self.assertEqual(result, "mock_value") def test_get_manifest_invalid_escrow(self): diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_utils.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_utils.py index 3a61638d6b..c4f22de6bb 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_utils.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_utils.py @@ -176,7 +176,7 @@ def test_get_escrows_with_status_array(self): "intermediateResultsUrl": "https://example.com", "launcher": "0x1234567890123456789012345678901234567891", "manifestHash": "0x1234567890123456789012345678901234567891", - "manifestUrl": "https://example.com", + "manifest": "https://example.com", "recordingOracle": "0x1234567890123456789012345678901234567891", "reputationOracle": "0x1234567890123456789012345678901234567891", "exchangeOracle": "0x1234567890123456789012345678901234567891", @@ -196,7 +196,7 @@ def test_get_escrows_with_status_array(self): "intermediateResultsUrl": "https://example.com", "launcher": "0x1234567890123456789012345678901234567891", "manifestHash": "0x1234567890123456789012345678901234567891", - "manifestUrl": "https://example.com", + "manifest": "https://example.com", "recordingOracle": "0x1234567890123456789012345678901234567891", "reputationOracle": "0x1234567890123456789012345678901234567891", "exchangeOracle": "0x1234567890123456789012345678901234567891", diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/statistics/test_statistics_utils.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/statistics/test_statistics_utils.py index c17523e5f7..4e53363602 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/statistics/test_statistics_utils.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/statistics/test_statistics_utils.py @@ -1,12 +1,13 @@ import unittest from datetime import datetime -from unittest.mock import MagicMock, patch +from unittest.mock import patch from human_protocol_sdk.constants import NETWORKS, ChainId from human_protocol_sdk.gql.hmtoken import get_holders_query from human_protocol_sdk.gql.statistics import ( get_event_day_data_query, get_escrow_statistics_query, + get_hmt_event_day_data_query, get_hmtoken_statistics_query, ) from human_protocol_sdk.statistics import ( @@ -147,7 +148,6 @@ def test_get_payment_statistics(self): { "timestamp": 1, "dailyPayoutCount": "4", - "dailyHMTPayoutAmount": "100", "dailyWorkerCount": "4", }, ], @@ -177,13 +177,7 @@ def test_get_payment_statistics(self): payment_statistics.daily_payments_data[0].timestamp, datetime.fromtimestamp(1), ) - self.assertEqual( - payment_statistics.daily_payments_data[0].total_amount_paid, 100 - ) self.assertEqual(payment_statistics.daily_payments_data[0].total_count, 4) - self.assertEqual( - payment_statistics.daily_payments_data[0].average_amount_per_worker, 25 - ) def test_get_hmt_statistics(self): with patch( @@ -207,6 +201,7 @@ def test_get_hmt_statistics(self): NETWORKS[ChainId.LOCALHOST], query=get_hmtoken_statistics_query, options=None, + use_hmt_subgraph=True, ) self.assertEqual(hmt_statistics.total_transfer_amount, 100) @@ -245,6 +240,7 @@ def test_get_hmt_holders(self): "orderDirection": param.order_direction, }, options=None, + use_hmt_subgraph=True, ) self.assertEqual(len(holders), 2) @@ -283,7 +279,7 @@ def test_get_hmt_daily_data(self): mock_function.assert_any_call( NETWORKS[ChainId.LOCALHOST], - query=get_event_day_data_query(param), + query=get_hmt_event_day_data_query(param), params={ "from": 1683811973, "to": 1683812007, @@ -292,6 +288,7 @@ def test_get_hmt_daily_data(self): "orderDirection": "asc", }, options=None, + use_hmt_subgraph=True, ) self.assertEqual(len(hmt_statistics), 1) diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/test_utils.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/test_utils.py index 54e5bc65bb..9c9fd026d5 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/test_utils.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/test_utils.py @@ -72,7 +72,12 @@ def test_returns_response_without_options(self): result = custom_gql_fetch(self.network, self.query, self.variables) self.assertEqual(result, expected) - mock_fetch.assert_called_once_with(self.network, self.query, self.variables) + mock_fetch.assert_called_once_with( + self.network, + self.query, + self.variables, + use_hmt_subgraph=False, + ) def test_retries_on_indexer_error_and_succeeds(self): options = SubgraphOptions(max_retries=2, base_delay=100) @@ -151,7 +156,7 @@ def test_routes_requests_to_specific_indexer(self): self.assertEqual(result, expected) mock_fetch.assert_called_once_with( - self.network, self.query, self.variables, "0xabc123" + self.network, self.query, self.variables, "0xabc123", False ) def test_raises_when_indexer_without_api_key(self): @@ -184,6 +189,70 @@ def test_fetch_subgraph_adds_authorization_header(self): {"Authorization": "Bearer token"}, ) + def test_fetch_hmt_subgraph_uses_hmt_public_url_without_api_key(self): + network = { + "subgraph_url": "http://subgraph", + "subgraph_url_api_key": "http://subgraph-with-key", + "hmt_subgraph_url": "http://hmt-subgraph", + "hmt_subgraph_url_api_key": "http://hmt-subgraph-with-key", + } + + with patch("human_protocol_sdk.utils.requests.post") as mock_post: + mock_post.return_value.status_code = 200 + mock_post.return_value.json.return_value = {"data": {}} + + _fetch_subgraph_data( + network, + self.query, + self.variables, + use_hmt_subgraph=True, + ) + + self.assertEqual(mock_post.call_args.args[0], "http://hmt-subgraph") + + def test_fetch_hmt_subgraph_uses_hmt_api_key_url_with_api_key(self): + network = { + "subgraph_url": "http://subgraph", + "subgraph_url_api_key": "http://subgraph-with-key", + "hmt_subgraph_url": "http://hmt-subgraph", + "hmt_subgraph_url_api_key": "http://hmt-subgraph-with-key", + } + + with patch.dict(os.environ, {"SUBGRAPH_API_KEY": "token"}, clear=True): + with patch("human_protocol_sdk.utils.requests.post") as mock_post: + mock_post.return_value.status_code = 200 + mock_post.return_value.json.return_value = {"data": {}} + + _fetch_subgraph_data( + network, + self.query, + self.variables, + use_hmt_subgraph=True, + ) + + self.assertEqual(mock_post.call_args.args[0], "http://hmt-subgraph-with-key") + + def test_fetch_hmt_subgraph_falls_back_to_hmt_public_url_for_api_key_mode(self): + network = { + "subgraph_url": "http://subgraph", + "subgraph_url_api_key": "http://subgraph-with-key", + "hmt_subgraph_url": "http://hmt-subgraph", + } + + with patch.dict(os.environ, {"SUBGRAPH_API_KEY": "token"}, clear=True): + with patch("human_protocol_sdk.utils.requests.post") as mock_post: + mock_post.return_value.status_code = 200 + mock_post.return_value.json.return_value = {"data": {}} + + _fetch_subgraph_data( + network, + self.query, + self.variables, + use_hmt_subgraph=True, + ) + + self.assertEqual(mock_post.call_args.args[0], "http://hmt-subgraph") + class TestAttachIndexerId(unittest.TestCase): def test_converts_subgraph_path_to_deployment(self): diff --git a/packages/sdk/typescript/human-protocol-sdk/example/statistics.ts b/packages/sdk/typescript/human-protocol-sdk/example/statistics.ts index 9a3c0f8f2b..f5979df609 100644 --- a/packages/sdk/typescript/human-protocol-sdk/example/statistics.ts +++ b/packages/sdk/typescript/human-protocol-sdk/example/statistics.ts @@ -38,14 +38,7 @@ import { ChainId } from '../src/enums'; const paymentStatistics = await StatisticsUtils.getPaymentStatistics(networkData); - console.log('Payment statistics:', { - ...paymentStatistics, - dailyPaymentsData: paymentStatistics.dailyPaymentsData.map((p) => ({ - ...p, - totalAmountPaid: p.totalAmountPaid.toString(), - averageAmountPerWorker: p.averageAmountPerWorker.toString(), - })), - }); + console.log('Payment statistics:', paymentStatistics); const paymentStatisticsRange = await StatisticsUtils.getPaymentStatistics( networkData, @@ -55,14 +48,7 @@ import { ChainId } from '../src/enums'; } ); - console.log('Payment statistics from 5/8 - 6/8:', { - ...paymentStatisticsRange, - dailyPaymentsData: paymentStatisticsRange.dailyPaymentsData.map((p) => ({ - ...p, - totalAmountPaid: p.totalAmountPaid.toString(), - averageAmountPerWorker: p.averageAmountPerWorker.toString(), - })), - }); + console.log('Payment statistics from 5/8 - 6/8:', paymentStatisticsRange); const hmtStatistics = await StatisticsUtils.getHMTStatistics(networkData); diff --git a/packages/sdk/typescript/human-protocol-sdk/src/constants.ts b/packages/sdk/typescript/human-protocol-sdk/src/constants.ts index 6a9006c802..92d20d79e8 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/constants.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/constants.ts @@ -111,11 +111,15 @@ export const NETWORKS: { stakingAddress: '0xffE496683F842a923110415b7278ded3F265f2C5', kvstoreAddress: '0x724AeFC243EdacCA27EAB86D3ec5a76Af4436Fc7', subgraphUrl: - 'https://api.studio.thegraph.com/query/74256/amoy/version/latest', + 'https://api.studio.thegraph.com/query/74256/human-amoy/version/latest', subgraphUrlApiKey: - 'https://gateway.thegraph.com/api/deployments/id/QmcLwLMw3UzCSbNbjegrpNu6PB3kAd67xquuyaVWvc5Q7Q', + 'https://gateway.thegraph.com/api/deployments/id/QmP7D2irmx3WP2pP97QTcQxiv91KEyTGftHBy98Foh7WHg', oldSubgraphUrl: '', oldFactoryAddress: '', + hmtSubgraphUrl: + 'https://api.studio.thegraph.com/query/74256/hmt-stats-amoy/version/latest', + hmtSubgraphUrlApiKey: + 'https://gateway.thegraph.com/api/deployments/id/QmZJYBQKyrsEJHQwNQ8JhkKcB5G8zQdrcwnr7ecGRNTBit', }, [ChainId.LOCALHOST]: { chainId: ChainId.LOCALHOST, diff --git a/packages/sdk/typescript/human-protocol-sdk/src/error.ts b/packages/sdk/typescript/human-protocol-sdk/src/error.ts index 7371a6baa3..86334c52e2 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/error.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/error.ts @@ -180,20 +180,6 @@ export const ErrorInvalidManifest = new Error('Invalid manifest'); */ export const ErrorNoURLprovided = new Error('No URL provided'); -/** - * @constant {Error} - Fee must be between 0 and 100. - */ -export const ErrorFeeMustBeBetweenZeroAndHundred = new Error( - 'Fee must be between 0 and 100' -); - -/** - * @constant {Error} - Total fee must be less than 100. - */ -export const ErrorTotalFeeMustBeLessThanHundred = new Error( - 'Total fee must be less than 100' -); - /** * @constant {Error} - Recipient cannot be an empty array. */ diff --git a/packages/sdk/typescript/human-protocol-sdk/src/escrow/escrow_client.ts b/packages/sdk/typescript/human-protocol-sdk/src/escrow/escrow_client.ts index 63e0eb72fc..a5d745e38b 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/escrow/escrow_client.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/escrow/escrow_client.ts @@ -34,7 +34,6 @@ import { ErrorRecipientCannotBeEmptyArray, ErrorStoreResultsVersion, ErrorTooManyRecipients, - ErrorTotalFeeMustBeLessThanHundred, ErrorTransferEventNotFoundInTransactionLogs, ErrorUnsupportedChainID, InvalidEthereumAddressError, @@ -216,9 +215,6 @@ export class EscrowClient extends BaseEthersClient { recordingOracle, reputationOracle, exchangeOracle, - recordingOracleFee, - reputationOracleFee, - exchangeOracleFee, manifest, manifestHash, } = escrowConfig; @@ -235,18 +231,6 @@ export class EscrowClient extends BaseEthersClient { throw ErrorInvalidExchangeOracleAddressProvided; } - if ( - recordingOracleFee <= 0 || - reputationOracleFee <= 0 || - exchangeOracleFee <= 0 - ) { - throw ErrorAmountMustBeGreaterThanZero; - } - - if (recordingOracleFee + reputationOracleFee + exchangeOracleFee > 100) { - throw ErrorTotalFeeMustBeLessThanHundred; - } - const isManifestValid = isValidUrl(manifest) || isValidJson(manifest); if (!isManifestValid) { throw ErrorInvalidManifest; @@ -271,8 +255,6 @@ export class EscrowClient extends BaseEthersClient { * @throws ErrorInvalidRecordingOracleAddressProvided If the recording oracle address is invalid * @throws ErrorInvalidReputationOracleAddressProvided If the reputation oracle address is invalid * @throws ErrorInvalidExchangeOracleAddressProvided If the exchange oracle address is invalid - * @throws ErrorAmountMustBeGreaterThanZero If any oracle fee is less than or equal to zero - * @throws ErrorTotalFeeMustBeLessThanHundred If the total oracle fees exceed 100 * @throws ErrorInvalidManifest If the manifest is not a valid URL or JSON string * @throws ErrorHashIsEmptyString If the manifest hash is empty * @throws ErrorLaunchedEventIsNotEmitted If the LaunchedV2 event is not emitted @@ -293,9 +275,6 @@ export class EscrowClient extends BaseEthersClient { * recordingOracle: '0xRecordingOracleAddress', * reputationOracle: '0xReputationOracleAddress', * exchangeOracle: '0xExchangeOracleAddress', - * recordingOracleFee: 5n, - * reputationOracleFee: 5n, - * exchangeOracleFee: 5n, * manifest: 'https://example.com/manifest.json', * manifestHash: 'manifestHash-123', * }; @@ -327,9 +306,6 @@ export class EscrowClient extends BaseEthersClient { recordingOracle, reputationOracle, exchangeOracle, - recordingOracleFee, - reputationOracleFee, - exchangeOracleFee, manifest, manifestHash, } = escrowConfig; @@ -344,9 +320,6 @@ export class EscrowClient extends BaseEthersClient { reputationOracle, recordingOracle, exchangeOracle, - reputationOracleFee, - recordingOracleFee, - exchangeOracleFee, manifest, manifestHash, overrides @@ -382,8 +355,6 @@ export class EscrowClient extends BaseEthersClient { * @throws ErrorInvalidRecordingOracleAddressProvided If the recording oracle address is invalid * @throws ErrorInvalidReputationOracleAddressProvided If the reputation oracle address is invalid * @throws ErrorInvalidExchangeOracleAddressProvided If the exchange oracle address is invalid - * @throws ErrorAmountMustBeGreaterThanZero If any oracle fee is less than or equal to zero - * @throws ErrorTotalFeeMustBeLessThanHundred If the total oracle fees exceed 100 * @throws ErrorInvalidManifest If the manifest is not a valid URL or JSON string * @throws ErrorHashIsEmptyString If the manifest hash is empty * @throws ErrorInvalidEscrowAddressProvided If the escrow address is invalid @@ -397,9 +368,6 @@ export class EscrowClient extends BaseEthersClient { * recordingOracle: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', * reputationOracle: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', * exchangeOracle: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', - * recordingOracleFee: 10n, - * reputationOracleFee: 10n, - * exchangeOracleFee: 10n, * manifest: 'http://localhost/manifest.json', * manifestHash: 'b5dad76bf6772c0f07fd5e048f6e75a5f86ee079', * }; @@ -416,9 +384,6 @@ export class EscrowClient extends BaseEthersClient { recordingOracle, reputationOracle, exchangeOracle, - recordingOracleFee, - reputationOracleFee, - exchangeOracleFee, manifest, manifestHash, } = escrowConfig; @@ -442,9 +407,6 @@ export class EscrowClient extends BaseEthersClient { reputationOracle, recordingOracle, exchangeOracle, - reputationOracleFee, - recordingOracleFee, - exchangeOracleFee, manifest, manifestHash, overrides @@ -1321,8 +1283,31 @@ export class EscrowClient extends BaseEthersClient { try { const escrowContract = this.getEscrowContract(escrowAddress); + const provider = this.runner.provider; + if (!provider) { + throw ErrorProviderDoesNotExist; + } - return escrowContract.manifestUrl(); + const manifestInterface = new ethers.Interface([ + 'function manifest() view returns (string)', + 'function manifestUrl() view returns (string)', + ]); + const target = escrowContract.target as string; + + const readManifestField = async (field: 'manifest' | 'manifestUrl') => { + const data = manifestInterface.encodeFunctionData(field); + const result = await provider.call({ to: target, data }); + return manifestInterface.decodeFunctionResult( + field, + result + )[0] as string; + }; + + try { + return await readManifestField('manifest'); + } catch { + return await readManifestField('manifestUrl'); + } } catch (e) { return throwError(e); } diff --git a/packages/sdk/typescript/human-protocol-sdk/src/graphql/queries/statistics.ts b/packages/sdk/typescript/human-protocol-sdk/src/graphql/queries/statistics.ts index 2135d3ca7c..2490d561ed 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/graphql/queries/statistics.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/graphql/queries/statistics.ts @@ -42,7 +42,12 @@ const EVENT_DAY_DATA_FRAGMENT = gql` dailyEscrowCount dailyWorkerCount dailyPayoutCount - dailyHMTPayoutAmount + } +`; + +const HMT_EVENT_DAY_DATA_FRAGMENT = gql` + fragment HMTEventDayDataFields on EventDayData { + timestamp dailyHMTTransferCount dailyHMTTransferAmount dailyUniqueSenders @@ -102,3 +107,34 @@ export const GET_EVENT_DAY_DATA_QUERY = (params: IStatisticsFilter) => { ${EVENT_DAY_DATA_FRAGMENT} `; }; + +export const GET_HMT_EVENT_DAY_DATA_QUERY = (params: IStatisticsFilter) => { + const { from, to } = params; + const WHERE_CLAUSE = ` + where: { + ${from !== undefined ? `timestamp_gte: $from` : ''} + ${to !== undefined ? `timestamp_lte: $to` : ''} + } + `; + + return gql` + query GetHMTDayData( + $from: Int, + $to: Int, + $orderDirection: String + $first: Int + $skip: Int + ) { + eventDayDatas( + ${WHERE_CLAUSE}, + orderBy: timestamp, + orderDirection: $orderDirection, + first: $first, + skip: $skip + ) { + ...HMTEventDayDataFields + } + } + ${HMT_EVENT_DAY_DATA_FRAGMENT} + `; +}; diff --git a/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts b/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts index fed501218f..295acaac4a 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/graphql/types.ts @@ -95,7 +95,10 @@ export type EventDayData = { dailyEscrowCount: string; dailyWorkerCount: string; dailyPayoutCount: string; - dailyHMTPayoutAmount: string; +}; + +export type HMTEventDayData = { + timestamp: string; dailyHMTTransferCount: string; dailyHMTTransferAmount: string; dailyUniqueSenders: string; diff --git a/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts b/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts index e9bca41378..cac654ae4c 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/interfaces.ts @@ -87,9 +87,6 @@ export interface IEscrowConfig { recordingOracle: string; reputationOracle: string; exchangeOracle: string; - recordingOracleFee: bigint; - reputationOracleFee: bigint; - exchangeOracleFee: bigint; manifest: string; manifestHash: string; } @@ -254,9 +251,7 @@ export interface IWorkerStatistics { export interface IDailyPayment { timestamp: number; - totalAmountPaid: bigint; totalCount: number; - averageAmountPerWorker: bigint; } export interface IPaymentStatistics { diff --git a/packages/sdk/typescript/human-protocol-sdk/src/statistics/statistics_utils.ts b/packages/sdk/typescript/human-protocol-sdk/src/statistics/statistics_utils.ts index 1cf4b4422c..aceb9e63e0 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/statistics/statistics_utils.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/statistics/statistics_utils.ts @@ -3,8 +3,10 @@ import { OrderDirection } from '../enums'; import { EscrowStatisticsData, EventDayData, + HMTEventDayData, GET_ESCROW_STATISTICS_QUERY, GET_EVENT_DAY_DATA_QUERY, + GET_HMT_EVENT_DAY_DATA_QUERY, GET_HMTOKEN_STATISTICS_QUERY, GET_HOLDERS_QUERY, HMTHolderData, @@ -23,6 +25,7 @@ import { } from '../interfaces'; import { NetworkData } from '../types'; import { + getHMTSubgraphUrl, getSubgraphUrl, getUnixTimestamp, customGqlFetch, @@ -253,9 +256,7 @@ export class StatisticsUtils { * ```ts * interface IDailyPayment { * timestamp: number; - * totalAmountPaid: bigint; * totalCount: number; - * averageAmountPerWorker: bigint; * }; * * interface IPaymentStatistics { @@ -274,14 +275,7 @@ export class StatisticsUtils { * * const networkData = NETWORKS[ChainId.POLYGON_AMOY]; * const paymentStats = await StatisticsUtils.getPaymentStatistics(networkData); - * console.log( - * 'Payment statistics:', - * paymentStats.dailyPaymentsData.map((p) => ({ - * ...p, - * totalAmountPaid: p.totalAmountPaid.toString(), - * averageAmountPerWorker: p.averageAmountPerWorker.toString(), - * })) - * ); + * console.log('Payment statistics:', paymentStats.dailyPaymentsData); * * const paymentStatsRange = await StatisticsUtils.getPaymentStatistics( * networkData, @@ -323,13 +317,7 @@ export class StatisticsUtils { return { dailyPaymentsData: eventDayDatas.map((eventDayData) => ({ timestamp: +eventDayData.timestamp * 1000, - totalAmountPaid: BigInt(eventDayData.dailyHMTPayoutAmount), totalCount: +eventDayData.dailyPayoutCount, - averageAmountPerWorker: - eventDayData.dailyWorkerCount === '0' - ? BigInt(0) - : BigInt(eventDayData.dailyHMTPayoutAmount) / - BigInt(eventDayData.dailyWorkerCount), })), }; } catch (e: any) { @@ -369,7 +357,7 @@ export class StatisticsUtils { options?: SubgraphOptions ): Promise { try { - const subgraphUrl = getSubgraphUrl(networkData); + const subgraphUrl = getHMTSubgraphUrl(networkData); const { hmtokenStatistics } = await customGqlFetch<{ hmtokenStatistics: HMTStatisticsData; }>(subgraphUrl, GET_HMTOKEN_STATISTICS_QUERY, options); @@ -412,7 +400,7 @@ export class StatisticsUtils { options?: SubgraphOptions ): Promise { try { - const subgraphUrl = getSubgraphUrl(networkData); + const subgraphUrl = getHMTSubgraphUrl(networkData); const { address, orderDirection } = params; const query = GET_HOLDERS_QUERY(address); @@ -490,17 +478,17 @@ export class StatisticsUtils { options?: SubgraphOptions ): Promise { try { - const subgraphUrl = getSubgraphUrl(networkData); + const subgraphUrl = getHMTSubgraphUrl(networkData); const first = filter.first !== undefined ? Math.min(filter.first, 1000) : 10; const skip = filter.skip || 0; const orderDirection = filter.orderDirection || OrderDirection.ASC; const { eventDayDatas } = await customGqlFetch<{ - eventDayDatas: EventDayData[]; + eventDayDatas: HMTEventDayData[]; }>( subgraphUrl, - GET_EVENT_DAY_DATA_QUERY(filter), + GET_HMT_EVENT_DAY_DATA_QUERY(filter), { from: filter.from ? getUnixTimestamp(filter.from) : undefined, to: filter.to ? getUnixTimestamp(filter.to) : undefined, diff --git a/packages/sdk/typescript/human-protocol-sdk/src/types.ts b/packages/sdk/typescript/human-protocol-sdk/src/types.ts index 9ced185a32..b6ea17852a 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/types.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/types.ts @@ -76,6 +76,14 @@ export type NetworkData = { * Subgraph URL API key */ subgraphUrlApiKey: string; + /** + * HMT statistics subgraph URL + */ + hmtSubgraphUrl?: string; + /** + * HMT statistics subgraph URL API key + */ + hmtSubgraphUrlApiKey?: string; /** * Old subgraph URL */ diff --git a/packages/sdk/typescript/human-protocol-sdk/src/utils.ts b/packages/sdk/typescript/human-protocol-sdk/src/utils.ts index 5d28485d4e..5a9de8a2e5 100644 --- a/packages/sdk/typescript/human-protocol-sdk/src/utils.ts +++ b/packages/sdk/typescript/human-protocol-sdk/src/utils.ts @@ -98,6 +98,26 @@ export const getSubgraphUrl = (networkData: NetworkData) => { return subgraphUrl; }; +/** + * Gets the HMT statistics subgraph URL for the given network. + * Falls back to the default subgraph URL when a dedicated HMT endpoint is not configured. + * + * @param networkData - The network data containing subgraph URLs + * @returns The HMT statistics subgraph URL with API key if available + */ +export const getHMTSubgraphUrl = (networkData: NetworkData) => { + let subgraphUrl = networkData.hmtSubgraphUrl || networkData.subgraphUrl; + if (process.env.SUBGRAPH_API_KEY) { + subgraphUrl = + networkData.hmtSubgraphUrlApiKey || networkData.subgraphUrlApiKey; + } else if (networkData.chainId !== ChainId.LOCALHOST) { + // eslint-disable-next-line no-console + console.warn(WarnSubgraphApiKeyNotProvided); + } + + return subgraphUrl; +}; + /** * Converts a Date object to Unix timestamp (seconds since epoch). * diff --git a/packages/sdk/typescript/human-protocol-sdk/test/escrow.test.ts b/packages/sdk/typescript/human-protocol-sdk/test/escrow.test.ts index 0e1bb87b9d..b90e823e25 100644 --- a/packages/sdk/typescript/human-protocol-sdk/test/escrow.test.ts +++ b/packages/sdk/typescript/human-protocol-sdk/test/escrow.test.ts @@ -37,7 +37,6 @@ import { ErrorSigner, ErrorStoreResultsVersion, ErrorTooManyRecipients, - ErrorTotalFeeMustBeLessThanHundred, ErrorUnsupportedChainID, ErrorInvalidManifest, InvalidEthereumAddressError, @@ -75,6 +74,7 @@ describe('EscrowClient', () => { mockProvider = { provider: { getNetwork: vi.fn().mockResolvedValue({ chainId: ChainId.LOCALHOST }), + call: vi.fn(), }, }; mockSigner = { @@ -104,7 +104,7 @@ describe('EscrowClient', () => { remainingFunds: vi.fn(), reservedFunds: vi.fn(), manifestHash: vi.fn(), - manifestUrl: vi.fn(), + manifest: vi.fn(), finalResultsUrl: vi.fn(), token: vi.fn(), status: vi.fn(), @@ -119,6 +119,7 @@ describe('EscrowClient', () => { intermediateResultsHash: vi.fn(), launcher: vi.fn(), escrowFactory: vi.fn(), + target: ethers.ZeroAddress, }; mockEscrowFactoryContract = { @@ -431,94 +432,6 @@ describe('EscrowClient', () => { ).rejects.toThrow(ErrorInvalidExchangeOracleAddressProvided); }); - test('should throw an error if recordingOracleFee <= 0', async () => { - const escrowConfig = { - recordingOracle: ethers.ZeroAddress, - reputationOracle: ethers.ZeroAddress, - exchangeOracle: ethers.ZeroAddress, - recordingOracleFee: 0n, - reputationOracleFee: 10n, - exchangeOracleFee: 10n, - manifest: VALID_URL, - manifestHash: FAKE_HASH, - }; - - await expect( - escrowClient.createFundAndSetupEscrow( - tokenAddress, - 10n, - jobRequesterId, - escrowConfig - ) - ).rejects.toThrow(ErrorAmountMustBeGreaterThanZero); - }); - - test('should throw an error if reputationOracleFee <= 0', async () => { - const escrowConfig = { - recordingOracle: ethers.ZeroAddress, - reputationOracle: ethers.ZeroAddress, - exchangeOracle: ethers.ZeroAddress, - recordingOracleFee: 10n, - reputationOracleFee: 0n, - exchangeOracleFee: 10n, - manifest: VALID_URL, - hash: FAKE_HASH, - }; - - await expect( - escrowClient.createFundAndSetupEscrow( - tokenAddress, - 10n, - jobRequesterId, - escrowConfig - ) - ).rejects.toThrow(ErrorAmountMustBeGreaterThanZero); - }); - - test('should throw an error if exchangeOracleFee <= 0', async () => { - const escrowConfig = { - recordingOracle: ethers.ZeroAddress, - reputationOracle: ethers.ZeroAddress, - exchangeOracle: ethers.ZeroAddress, - recordingOracleFee: 10n, - reputationOracleFee: 10n, - exchangeOracleFee: 0n, - manifest: VALID_URL, - manifestHash: FAKE_HASH, - }; - - await expect( - escrowClient.createFundAndSetupEscrow( - tokenAddress, - 10n, - jobRequesterId, - escrowConfig - ) - ).rejects.toThrow(ErrorAmountMustBeGreaterThanZero); - }); - - test('should throw an error if total fee > 100', async () => { - const escrowConfig = { - recordingOracle: ethers.ZeroAddress, - reputationOracle: ethers.ZeroAddress, - exchangeOracle: ethers.ZeroAddress, - recordingOracleFee: 40n, - reputationOracleFee: 40n, - exchangeOracleFee: 40n, - manifest: VALID_URL, - manifestHash: FAKE_HASH, - }; - - await expect( - escrowClient.createFundAndSetupEscrow( - tokenAddress, - 10n, - jobRequesterId, - escrowConfig - ) - ).rejects.toThrow(ErrorTotalFeeMustBeLessThanHundred); - }); - test('should throw an error if manifest is an empty string', async () => { const escrowConfig = { recordingOracle: ethers.ZeroAddress, @@ -595,9 +508,6 @@ describe('EscrowClient', () => { escrowConfig.reputationOracle, escrowConfig.recordingOracle, escrowConfig.exchangeOracle, - escrowConfig.reputationOracleFee, - escrowConfig.recordingOracleFee, - escrowConfig.exchangeOracleFee, escrowConfig.manifest, escrowConfig.manifestHash, {} @@ -634,9 +544,6 @@ describe('EscrowClient', () => { escrowConfig.reputationOracle, escrowConfig.recordingOracle, escrowConfig.exchangeOracle, - escrowConfig.reputationOracleFee, - escrowConfig.recordingOracleFee, - escrowConfig.exchangeOracleFee, escrowConfig.manifest, escrowConfig.manifestHash, {} @@ -677,9 +584,6 @@ describe('EscrowClient', () => { escrowConfig.reputationOracle, escrowConfig.recordingOracle, escrowConfig.exchangeOracle, - escrowConfig.reputationOracleFee, - escrowConfig.recordingOracleFee, - escrowConfig.exchangeOracleFee, escrowConfig.manifest, escrowConfig.manifestHash, txOptions @@ -767,9 +671,6 @@ describe('EscrowClient', () => { escrowConfig.reputationOracle, escrowConfig.recordingOracle, escrowConfig.exchangeOracle, - escrowConfig.reputationOracleFee, - escrowConfig.recordingOracleFee, - escrowConfig.exchangeOracleFee, escrowConfig.manifest, escrowConfig.manifestHash, {} @@ -866,82 +767,6 @@ describe('EscrowClient', () => { ).rejects.toThrow(ErrorEscrowAddressIsNotProvidedByFactory); }); - test('should throw an error if recordingOracleFee <= 0', async () => { - const escrowConfig = { - recordingOracle: ethers.ZeroAddress, - reputationOracle: ethers.ZeroAddress, - exchangeOracle: ethers.ZeroAddress, - recordingOracleFee: 0n, - reputationOracleFee: 10n, - exchangeOracleFee: 10n, - manifest: VALID_URL, - manifestHash: FAKE_HASH, - }; - - escrowClient.escrowFactoryContract.hasEscrow.mockReturnValue(true); - - await expect( - escrowClient.setup(ethers.ZeroAddress, escrowConfig) - ).rejects.toThrow(ErrorAmountMustBeGreaterThanZero); - }); - - test('should throw an error if reputationOracleFee <= 0', async () => { - const escrowConfig = { - recordingOracle: ethers.ZeroAddress, - reputationOracle: ethers.ZeroAddress, - exchangeOracle: ethers.ZeroAddress, - recordingOracleFee: 10n, - reputationOracleFee: 0n, - exchangeOracleFee: 10n, - manifest: VALID_URL, - manifestHash: FAKE_HASH, - }; - - escrowClient.escrowFactoryContract.hasEscrow.mockReturnValue(true); - - await expect( - escrowClient.setup(ethers.ZeroAddress, escrowConfig) - ).rejects.toThrow(ErrorAmountMustBeGreaterThanZero); - }); - - test('should throw an error if exchangeOracleFee <= 0', async () => { - const escrowConfig = { - recordingOracle: ethers.ZeroAddress, - reputationOracle: ethers.ZeroAddress, - exchangeOracle: ethers.ZeroAddress, - recordingOracleFee: 10n, - reputationOracleFee: 10n, - exchangeOracleFee: 0n, - manifest: VALID_URL, - manifestHash: FAKE_HASH, - }; - - escrowClient.escrowFactoryContract.hasEscrow.mockReturnValue(true); - - await expect( - escrowClient.setup(ethers.ZeroAddress, escrowConfig) - ).rejects.toThrow(ErrorAmountMustBeGreaterThanZero); - }); - - test('should throw an error if total fee > 100', async () => { - const escrowConfig = { - recordingOracle: ethers.ZeroAddress, - reputationOracle: ethers.ZeroAddress, - exchangeOracle: ethers.ZeroAddress, - recordingOracleFee: 40n, - reputationOracleFee: 40n, - exchangeOracleFee: 40n, - manifest: VALID_URL, - manifestHash: FAKE_HASH, - }; - - escrowClient.escrowFactoryContract.hasEscrow.mockReturnValue(true); - - await expect( - escrowClient.setup(ethers.ZeroAddress, escrowConfig) - ).rejects.toThrow(ErrorTotalFeeMustBeLessThanHundred); - }); - test('should throw an error if manifest is an empty string', async () => { const escrowConfig = { recordingOracle: ethers.ZeroAddress, @@ -986,9 +811,6 @@ describe('EscrowClient', () => { ethers.ZeroAddress, ethers.ZeroAddress, ethers.ZeroAddress, - 10n, - 10n, - 10n, '{"foo":"bar"}', FAKE_HASH, {} @@ -1039,9 +861,6 @@ describe('EscrowClient', () => { ethers.ZeroAddress, ethers.ZeroAddress, ethers.ZeroAddress, - 10n, - 10n, - 10n, VALID_URL, FAKE_HASH, {} @@ -1071,9 +890,6 @@ describe('EscrowClient', () => { ethers.ZeroAddress, ethers.ZeroAddress, ethers.ZeroAddress, - 10n, - 10n, - 10n, VALID_URL, FAKE_HASH, {} @@ -1107,9 +923,6 @@ describe('EscrowClient', () => { ethers.ZeroAddress, ethers.ZeroAddress, ethers.ZeroAddress, - 10n, - 10n, - 10n, VALID_URL, FAKE_HASH, txOptions @@ -3052,41 +2865,58 @@ describe('EscrowClient', () => { ); }); - test('should successfully getManifestUrl', async () => { + test('should successfully getManifest via manifest()', async () => { const escrowAddress = ethers.ZeroAddress; const url = FAKE_URL; + const manifestInterface = new ethers.Interface([ + 'function manifest() view returns (string)', + 'function manifestUrl() view returns (string)', + ]); escrowClient.escrowFactoryContract.hasEscrow.mockReturnValue(true); - escrowClient.escrowContract.manifestUrl.mockReturnValue(url); + mockProvider.provider.call.mockResolvedValueOnce( + manifestInterface.encodeFunctionResult('manifest', [url]) + ); const manifestUrl = await escrowClient.getManifest(escrowAddress); expect(manifestUrl).toEqual(url); - expect(escrowClient.escrowContract.manifestUrl).toHaveBeenCalledWith(); + expect(mockProvider.provider.call).toHaveBeenCalledTimes(1); }); - test('should return the manifest string', async () => { + test('should fallback to manifestUrl() for legacy contracts', async () => { const escrowAddress = ethers.ZeroAddress; const manifestString = '{"foo":"bar"}'; + const manifestInterface = new ethers.Interface([ + 'function manifest() view returns (string)', + 'function manifestUrl() view returns (string)', + ]); + escrowClient.escrowFactoryContract.hasEscrow.mockReturnValue(true); - escrowClient.escrowContract.manifestUrl = vi - .fn() - .mockReturnValue(manifestString); + mockProvider.provider.call + .mockRejectedValueOnce(new Error('manifest() not found')) + .mockResolvedValueOnce( + manifestInterface.encodeFunctionResult('manifestUrl', [ + manifestString, + ]) + ); + const result = await escrowClient.getManifest(escrowAddress); + expect(result).toBe(manifestString); + expect(mockProvider.provider.call).toHaveBeenCalledTimes(2); }); - test('should throw an error if getManifestUrl fails', async () => { + test('should throw an error if manifest() and manifestUrl() fail', async () => { const escrowAddress = ethers.ZeroAddress; escrowClient.escrowFactoryContract.hasEscrow.mockReturnValue(true); - escrowClient.escrowContract.manifestUrl.mockRejectedValueOnce( - new Error() - ); + mockProvider.provider.call + .mockRejectedValueOnce(new Error('manifest() failed')) + .mockRejectedValueOnce(new Error('manifestUrl() failed')); await expect(escrowClient.getManifest(escrowAddress)).rejects.toThrow(); - - expect(escrowClient.escrowContract.manifestUrl).toHaveBeenCalledWith(); + expect(mockProvider.provider.call).toHaveBeenCalledTimes(2); }); }); diff --git a/packages/sdk/typescript/human-protocol-sdk/test/statistics.test.ts b/packages/sdk/typescript/human-protocol-sdk/test/statistics.test.ts index e3b152056f..2ddd183cee 100644 --- a/packages/sdk/typescript/human-protocol-sdk/test/statistics.test.ts +++ b/packages/sdk/typescript/human-protocol-sdk/test/statistics.test.ts @@ -12,6 +12,7 @@ import { StatisticsUtils } from '../src/statistics'; import { GET_ESCROW_STATISTICS_QUERY, GET_EVENT_DAY_DATA_QUERY, + GET_HMT_EVENT_DAY_DATA_QUERY, GET_HOLDERS_QUERY, } from '../src/graphql/queries'; @@ -171,8 +172,6 @@ describe('StatisticsUtils', () => { { timestamp: 1, dailyPayoutCount: '4', - dailyHMTPayoutAmount: '100', - dailyWorkerCount: '4', }, ], }); @@ -202,9 +201,7 @@ describe('StatisticsUtils', () => { dailyPaymentsData: [ { timestamp: 1000, - totalAmountPaid: ethers.toBigInt(100), totalCount: 4, - averageAmountPerWorker: ethers.toBigInt(25), }, ], }); @@ -447,7 +444,7 @@ describe('StatisticsUtils', () => { expect(gqlFetchSpy).toHaveBeenCalledWith( 'https://api.studio.thegraph.com/query/74256/polygon/version/latest', - GET_EVENT_DAY_DATA_QUERY({ from, to }), + GET_HMT_EVENT_DAY_DATA_QUERY({ from, to }), { from: Math.floor(from.getTime() / 1000), to: Math.floor(to.getTime() / 1000), diff --git a/packages/sdk/typescript/subgraph/config/amoy.json b/packages/sdk/typescript/subgraph/config/amoy.json deleted file mode 100644 index 2253b3495f..0000000000 --- a/packages/sdk/typescript/subgraph/config/amoy.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "network": "polygon-amoy", - "description": "HUMAN subgraph on Amoy Testnet", - "EscrowFactory": { - "address": "0xAFf5a986A530ff839d49325A5dF69F96627E8D29", - "startBlock": 5773000, - "abi": "../../../../node_modules/@human-protocol/core/abis/EscrowFactory.json" - }, - "HMToken": { - "address": "0x792abbcC99c01dbDec49c9fa9A828a186Da45C33", - "startBlock": 5769546, - "abi": "../../../../node_modules/@human-protocol/core/abis/HMToken.json", - "totalSupply": "1000000000000000000000000000", - "contractDeployer": "0xF3D9a0ba9FA14273C515e519DFD0826Ff87d5164" - }, - "Escrow": { - "abi": "../../../../node_modules/@human-protocol/core/abis/Escrow.json" - }, - "KVStore": { - "address": "0x724AeFC243EdacCA27EAB86D3ec5a76Af4436Fc7", - "startBlock": 5773002, - "abi": "../../../../node_modules/@human-protocol/core/abis/KVStore.json" - }, - "Staking": { - "address": "0xffE496683F842a923110415b7278ded3F265f2C5", - "startBlock": 14983952, - "abi": "../../../../node_modules/@human-protocol/core/abis/Staking.json" - } -} diff --git a/packages/sdk/typescript/subgraph/config/bsc-testnet.json b/packages/sdk/typescript/subgraph/config/bsc-testnet.json deleted file mode 100644 index aaf5db6347..0000000000 --- a/packages/sdk/typescript/subgraph/config/bsc-testnet.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "network": "chapel", - "description": "Human subgraph on Binance Smart Chain testnet", - "EscrowFactory": { - "address": "0x2bfA592DBDaF434DDcbb893B1916120d181DAD18", - "startBlock": 26716359, - "abi": "../../../../node_modules/@human-protocol/core/abis/EscrowFactory.json" - }, - "HMToken": { - "address": "0xE3D74BBFa45B4bCa69FF28891fBE392f4B4d4e4d", - "startBlock": 26716354, - "abi": "../../../../node_modules/@human-protocol/core/abis/HMToken.json", - "totalSupply": "1000000000000000000000000000", - "contractDeployer": "0xf183b3B34E70Dd17859455389A3aB54D49D41e6f" - }, - "Escrow": { - "abi": "../../../../node_modules/@human-protocol/core/abis/Escrow.json" - }, - "Staking": { - "address": "0xD6D347ba6987519B4e42EcED43dF98eFf5465a23", - "startBlock": 45938762, - "abi": "../../../../node_modules/@human-protocol/core/abis/Staking.json" - }, - "KVStore": { - "address": "0x32e27177BA6Ea91cf28dfd91a0Da9822A4b74EcF", - "startBlock": 34883905, - "abi": "../../../../node_modules/@human-protocol/core/abis/KVStore.json" - }, - "LegacyEscrowFactory": { - "address": "0xaae6a2646c1f88763e62e0cd08ad050ea66ac46f", - "startBlock": 23632686, - "abi": "../../../../node_modules/@human-protocol/core/abis/legacy/EscrowFactory.json" - }, - "LegacyEscrow": { - "abi": "../../../../node_modules/@human-protocol/core/abis/legacy/Escrow.json" - } -} diff --git a/packages/sdk/typescript/subgraph/config/bsc.json b/packages/sdk/typescript/subgraph/config/bsc.json deleted file mode 100644 index cfbb32fd9b..0000000000 --- a/packages/sdk/typescript/subgraph/config/bsc.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "network": "bsc", - "description": "Human subgraph on BSC mainnet", - "EscrowFactory": { - "address": "0x92FD968AcBd521c232f5fB8c33b342923cC72714", - "startBlock": 28745625, - "abi": "../../../../node_modules/@human-protocol/core/abis/EscrowFactory.json" - }, - "HMToken": { - "address": "0x711Fd6ab6d65A98904522d4e3586F492B989c527", - "startBlock": 25383172, - "abi": "../../../../node_modules/@human-protocol/core/abis/HMToken.json", - "totalSupply": "0", - "contractDeployer": "bridge" - }, - "Escrow": { - "abi": "../../../../node_modules/@human-protocol/core/abis/Escrow.json" - }, - "Staking": { - "address": "0xE24e5C08E28331D24758b69A5E9f383D2bDD1c98", - "startBlock": 45120420, - "abi": "../../../../node_modules/@human-protocol/core/abis/Staking.json" - }, - "KVStore": { - "address": "0x21A0C4CED7aE447fCf87D9FE3A29FA9B3AB20Ff1", - "startBlock": 33941535, - "abi": "../../../../node_modules/@human-protocol/core/abis/KVStore.json" - }, - "LegacyEscrowFactory": { - "address": "0xc88bC422cAAb2ac8812de03176402dbcA09533f4", - "startBlock": 20689161, - "abi": "../../../../node_modules/@human-protocol/core/abis/legacy/EscrowFactory.json" - }, - "LegacyEscrow": { - "abi": "../../../../node_modules/@human-protocol/core/abis/legacy/Escrow.json" - } -} diff --git a/packages/sdk/typescript/subgraph/config/ethereum.json b/packages/sdk/typescript/subgraph/config/ethereum.json deleted file mode 100644 index 4cf773b86c..0000000000 --- a/packages/sdk/typescript/subgraph/config/ethereum.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "network": "mainnet", - "description": "Human subgraph on Ethereum Mainnet", - "EscrowFactory": { - "address": "0xD9c75a1Aa4237BB72a41E5E26bd8384f10c1f55a", - "startBlock": 16924057, - "abi": "../../../../node_modules/@human-protocol/core/abis/EscrowFactory.json" - }, - "HMToken": { - "address": "0xd1ba9BAC957322D6e8c07a160a3A8dA11A0d2867", - "startBlock": 12184475, - "abi": "../../../../node_modules/@human-protocol/core/abis/HMToken.json", - "totalSupply": "1000000000000000000000000000", - "contractDeployer": "0x3895d913a9A231d2B215F402c528511B569C676D" - }, - "Escrow": { - "abi": "../../../../node_modules/@human-protocol/core/abis/Escrow.json" - }, - "KVStore": { - "address": "0xB6d36B1CDaD50302BCB3DB43bAb0D349458e1b8D", - "startBlock": 18683644, - "abi": "../../../../node_modules/@human-protocol/core/abis/KVStore.json" - }, - "Staking": { - "address": "0xEf6Da3aB52c33925Be3F84038193a7e1331F51E6", - "startBlock": 21464165, - "abi": "../../../../node_modules/@human-protocol/core/abis/Staking.json" - }, - "LegacyEscrowFactory": { - "address": "0xD9c75a1Aa4237BB72a41E5E26bd8384f10c1f55a", - "startBlock": 16924057, - "abi": "../../../../node_modules/@human-protocol/core/abis/legacy/EscrowFactory.json" - }, - "LegacyEscrow": { - "abi": "../../../../node_modules/@human-protocol/core/abis/legacy/Escrow.json" - } -} diff --git a/packages/sdk/typescript/subgraph/config/localhost.json b/packages/sdk/typescript/subgraph/config/localhost.json deleted file mode 100644 index 0f617bae82..0000000000 --- a/packages/sdk/typescript/subgraph/config/localhost.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "network": "localhost", - "description": "Human subgraph on localhost", - "EscrowFactory": { - "address": "0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9", - "startBlock": 5, - "abi": "../../../../node_modules/@human-protocol/core/abis/EscrowFactory.json" - }, - "HMToken": { - "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "startBlock": 1, - "abi": "../../../../node_modules/@human-protocol/core/abis/HMToken.json", - "totalSupply": "1000000000000000000000000000", - "contractDeployer": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266" - }, - "Escrow": { - "abi": "../../../../node_modules/@human-protocol/core/abis/Escrow.json" - }, - "Staking": { - "address": "0xe7f1725e7734ce288f8367e1bb143e90bb3f0512", - "startBlock": 3, - "abi": "../../../../node_modules/@human-protocol/core/abis/Staking.json" - }, - "KVStore": { - "address": "0xdc64a140aa3e981100a9beca4e685f962f0cf6c9", - "startBlock": 6, - "abi": "../../../../node_modules/@human-protocol/core/abis/KVStore.json" - } -} diff --git a/packages/sdk/typescript/subgraph/config/polygon.json b/packages/sdk/typescript/subgraph/config/polygon.json deleted file mode 100644 index 82ea7715ad..0000000000 --- a/packages/sdk/typescript/subgraph/config/polygon.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "network": "matic", - "description": "Human subgraph on Polygon mainnet", - "EscrowFactory": { - "address": "0xBDBfD2cC708199C5640C6ECdf3B0F4A4C67AdfcB", - "startBlock": 38858552, - "abi": "../../../../node_modules/@human-protocol/core/abis/EscrowFactory.json" - }, - "HMToken": { - "address": "0xc748B2A084F8eFc47E086ccdDD9b7e67aEb571Bf", - "startBlock": 20181701, - "abi": "../../../../node_modules/@human-protocol/core/abis/HMToken.json", - "totalSupply": "0", - "contractDeployer": "bridge" - }, - "Escrow": { - "abi": "../../../../node_modules/@human-protocol/core/abis/Escrow.json" - }, - "Staking": { - "address": "0x01D115E9E8bF0C58318793624CC662a030D07F1D", - "startBlock": 65832028, - "abi": "../../../../node_modules/@human-protocol/core/abis/Staking.json" - }, - "KVStore": { - "address": "0xbcB28672F826a50B03EE91B28145EAbddA73B2eD", - "startBlock": 50567977, - "abi": "../../../../node_modules/@human-protocol/core/abis/KVStore.json" - }, - "LegacyEscrowFactory": { - "address": "0x45eBc3eAE6DA485097054ae10BA1A0f8e8c7f794", - "startBlock": 25426566, - "abi": "../../../../node_modules/@human-protocol/core/abis/legacy/EscrowFactory.json" - }, - "LegacyEscrow": { - "abi": "../../../../node_modules/@human-protocol/core/abis/legacy/Escrow.json" - } -} diff --git a/packages/sdk/typescript/subgraph/config/sepolia.json b/packages/sdk/typescript/subgraph/config/sepolia.json deleted file mode 100644 index 544eec59ef..0000000000 --- a/packages/sdk/typescript/subgraph/config/sepolia.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "network": "sepolia", - "description": "HUMAN subgraph on Sepolia Ethereum Testnet", - "EscrowFactory": { - "address": "0x5987A5558d961ee674efe4A8c8eB7B1b5495D3bf", - "startBlock": 7067993, - "abi": "../../../../node_modules/@human-protocol/core/abis/EscrowFactory.json" - }, - "HMToken": { - "address": "0x792abbcC99c01dbDec49c9fa9A828a186Da45C33", - "startBlock": 5716225, - "abi": "../../../../node_modules/@human-protocol/core/abis/HMToken.json", - "totalSupply": "1000000000000000000000000000", - "contractDeployer": "0xF3D9a0ba9FA14273C515e519DFD0826Ff87d5164" - }, - "Escrow": { - "abi": "../../../../node_modules/@human-protocol/core/abis/Escrow.json" - }, - "KVStore": { - "address": "0xCc0AF0635aa19fE799B6aFDBe28fcFAeA7f00a60", - "startBlock": 5716238, - "abi": "../../../../node_modules/@human-protocol/core/abis/KVStore.json" - }, - "Staking": { - "address": "0x2163e3A40032Af1C359ac731deaB48258b317890", - "startBlock": 7062708, - "abi": "../../../../node_modules/@human-protocol/core/abis/Staking.json" - }, - "LegacyEscrow": { - "abi": "../../../../node_modules/@human-protocol/core/abis/legacy/Escrow.json" - } -} diff --git a/packages/sdk/typescript/subgraph/matchstick.yaml b/packages/sdk/typescript/subgraph/matchstick.yaml deleted file mode 100644 index b6dd695aba..0000000000 --- a/packages/sdk/typescript/subgraph/matchstick.yaml +++ /dev/null @@ -1 +0,0 @@ -libsFolder: ../../../../node_modules diff --git a/packages/sdk/typescript/subgraph/src/mapping/HMTokenTemplate.ts b/packages/sdk/typescript/subgraph/src/mapping/HMTokenTemplate.ts deleted file mode 100644 index cb47c48fa0..0000000000 --- a/packages/sdk/typescript/subgraph/src/mapping/HMTokenTemplate.ts +++ /dev/null @@ -1,313 +0,0 @@ -import { Address, BigInt, Bytes, dataSource } from '@graphprotocol/graph-ts'; - -import { - Approval, - BulkApproval, - BulkTransfer, - Transfer, -} from '../../generated/HMToken/HMToken'; -import { - Escrow, - HMTApprovalEvent, - HMTBulkApprovalEvent, - HMTBulkTransferEvent, - HMTTransferEvent, - HMTokenStatistics, - Holder, - UniqueReceiver, - UniqueSender, -} from '../../generated/schema'; -import { toEventId } from './utils/event'; -import { ONE_BI, ONE_DAY, ZERO_BI } from './utils/number'; -import { getEventDayData } from './utils/dayUpdates'; -import { createTransaction } from './utils/transaction'; -import { toBytes } from './utils/string'; - -export const HMT_STATISTICS_ENTITY_ID = toBytes('hmt-statistics-id'); -export const TOTAL_SUPPLY = BigInt.fromString('{{ HMToken.totalSupply }}'); -export const CONTRACT_DEPLOYER = '{{ HMToken.contractDeployer }}'; - -function constructStatsEntity(): HMTokenStatistics { - const entity = new HMTokenStatistics(HMT_STATISTICS_ENTITY_ID); - - entity.totalTransferEventCount = BigInt.fromI32(0); - entity.totalApprovalEventCount = BigInt.fromI32(0); - entity.totalBulkApprovalEventCount = BigInt.fromI32(0); - entity.totalBulkTransferEventCount = BigInt.fromI32(0); - entity.totalValueTransfered = BigInt.fromI32(0); - entity.holders = BigInt.fromI32(0); - - return entity; -} - -export function createOrLoadUniqueSender( - dayStartTimestamp: BigInt, - timestamp: BigInt, - address: Address -): UniqueSender { - const id = Bytes.fromI32(dayStartTimestamp.toI32()).concat(address); - let uniqueSender = UniqueSender.load(id); - - if (!uniqueSender) { - uniqueSender = new UniqueSender(id); - uniqueSender.address = address; - uniqueSender.transferCount = ZERO_BI; - uniqueSender.timestamp = timestamp; - uniqueSender.save(); - } - - return uniqueSender; -} - -export function createOrLoadUniqueReceiver( - dayStartTimestamp: BigInt, - timestamp: BigInt, - address: Address -): UniqueReceiver { - const id = Bytes.fromI32(dayStartTimestamp.toI32()).concat(address); - let uniqueReceiver = UniqueReceiver.load(id); - - if (!uniqueReceiver) { - uniqueReceiver = new UniqueReceiver(id); - uniqueReceiver.address = address; - uniqueReceiver.receiveCount = ZERO_BI; - uniqueReceiver.timestamp = timestamp; - uniqueReceiver.save(); - } - - return uniqueReceiver; -} - -function updateHolders( - holderAddress: Address, - value: BigInt, - increase: boolean -): BigInt { - if ( - holderAddress.toHexString() == '0x0000000000000000000000000000000000000000' - ) - return ZERO_BI; - let count = ZERO_BI; - const id = holderAddress; - let holder = Holder.load(id); - - if (holder == null) { - holder = new Holder(id); - holder.address = holderAddress; - if (holderAddress.toHex() == CONTRACT_DEPLOYER) { - holder.balance = TOTAL_SUPPLY; - } else { - holder.balance = BigInt.fromI32(0); - } - } - const balanceBeforeTransfer = holder.balance; - holder.balance = increase - ? holder.balance.plus(value) - : holder.balance.minus(value); - - if (balanceBeforeTransfer.isZero() && !holder.balance.isZero()) { - count = ONE_BI; - } else if (!balanceBeforeTransfer.isZero() && holder.balance.isZero()) { - count = ONE_BI.neg(); - } - - holder.save(); - - return count; -} - -export function createOrLoadHMTStatistics(): HMTokenStatistics { - let statsEntity = HMTokenStatistics.load(HMT_STATISTICS_ENTITY_ID); - - if (!statsEntity) { - statsEntity = constructStatsEntity(); - } - - return statsEntity; -} - -export function handleTransfer(event: Transfer): void { - // Create HMTTransferEvent entity - const eventEntity = new HMTTransferEvent(toEventId(event)); - eventEntity.block = event.block.number; - eventEntity.timestamp = event.block.timestamp; - eventEntity.txHash = event.transaction.hash; - eventEntity.from = event.params._from; - eventEntity.to = event.params._to; - eventEntity.amount = event.params._value; - eventEntity.save(); - - // Update statistics - const statsEntity = createOrLoadHMTStatistics(); - statsEntity.totalTransferEventCount = - statsEntity.totalTransferEventCount.plus(ONE_BI); - statsEntity.totalValueTransfered = statsEntity.totalValueTransfered.plus( - event.params._value - ); - - const eventDayData = getEventDayData(event); - const escrow = Escrow.load(event.params._to); - if (escrow) { - // Update escrow balance - escrow.balance = escrow.balance.plus(event.params._value); - escrow.totalFundedAmount = escrow.totalFundedAmount.plus( - event.params._value - ); - escrow.save(); - - createTransaction( - event, - 'fund', - event.params._from, - dataSource.address(), - event.params._to, - event.params._to, - event.params._value, - dataSource.address() - ); - } else { - createTransaction( - event, - 'transfer', - event.params._from, - dataSource.address(), - event.params._to, - null, - event.params._value, - dataSource.address() - ); - } - - // Update holders - const diffHolders = updateHolders( - event.params._from, - event.params._value, - false - ).plus(updateHolders(event.params._to, event.params._value, true)); - statsEntity.holders = statsEntity.holders.plus(diffHolders); - - // Update event day data for HMT transfer - eventDayData.dailyHMTTransferCount = - eventDayData.dailyHMTTransferCount.plus(ONE_BI); - eventDayData.dailyHMTTransferAmount = - eventDayData.dailyHMTTransferAmount.plus(event.params._value); - - const timestamp = event.block.timestamp.toI32(); - const dayID = timestamp / ONE_DAY; - const dayStartTimestamp = dayID * ONE_DAY; - - // Update unique sender - const uniqueSender = createOrLoadUniqueSender( - BigInt.fromI32(dayStartTimestamp), - event.block.timestamp, - event.params._from - ); - if (uniqueSender.transferCount === ZERO_BI) { - eventDayData.dailyUniqueSenders = - eventDayData.dailyUniqueSenders.plus(ONE_BI); - } - uniqueSender.transferCount = uniqueSender.transferCount.plus( - event.params._value - ); - uniqueSender.save(); - - // Update unique receiver - const uniqueReceiver = createOrLoadUniqueReceiver( - BigInt.fromI32(dayStartTimestamp), - event.block.timestamp, - event.params._to - ); - if (uniqueReceiver.receiveCount === ZERO_BI) { - eventDayData.dailyUniqueReceivers = - eventDayData.dailyUniqueReceivers.plus(ONE_BI); - } - uniqueReceiver.receiveCount = uniqueReceiver.receiveCount.plus( - event.params._value - ); - uniqueReceiver.save(); - - eventDayData.save(); - statsEntity.save(); -} - -export function handleBulkTransfer(event: BulkTransfer): void { - createTransaction( - event, - 'transferBulk', - event.transaction.from, - dataSource.address(), - null, - null, - null, - dataSource.address() - ); - // Create HMTBulkTransferEvent entity - const eventEntity = new HMTBulkTransferEvent(toEventId(event)); - eventEntity.block = event.block.number; - eventEntity.timestamp = event.block.timestamp; - eventEntity.txHash = event.transaction.hash; - eventEntity.txId = event.params._txId; - eventEntity.bulkCount = event.params._bulkCount; - eventEntity.save(); - - // Update statistics - const statsEntity = createOrLoadHMTStatistics(); - statsEntity.totalBulkTransferEventCount = - statsEntity.totalBulkTransferEventCount.plus(ONE_BI); - statsEntity.save(); -} - -export function handleApproval(event: Approval): void { - createTransaction( - event, - 'approve', - event.params._owner, - dataSource.address(), - event.params._spender, - null, - event.params._value, - dataSource.address() - ); - // Create HMTApprovalEvent entity - const eventEntity = new HMTApprovalEvent(toEventId(event)); - eventEntity.block = event.block.number; - eventEntity.timestamp = event.block.timestamp; - eventEntity.txHash = event.transaction.hash; - eventEntity.owner = event.params._owner; - eventEntity.spender = event.params._spender; - eventEntity.amount = event.params._value; - eventEntity.save(); - - // Update statistics - const statsEntity = createOrLoadHMTStatistics(); - statsEntity.totalApprovalEventCount = - statsEntity.totalApprovalEventCount.plus(ONE_BI); - statsEntity.save(); -} - -export function handleBulkApproval(event: BulkApproval): void { - createTransaction( - event, - 'increaseApprovalBulk', - event.transaction.from, - dataSource.address(), - null, - null, - null, - dataSource.address() - ); - // Create HMTBulkApprovalEvent entity - const eventEntity = new HMTBulkApprovalEvent(toEventId(event)); - eventEntity.block = event.block.number; - eventEntity.timestamp = event.block.timestamp; - eventEntity.txHash = event.transaction.hash; - eventEntity.txId = event.params._txId; - eventEntity.bulkCount = event.params._bulkCount; - eventEntity.save(); - - // Update statistics - const statsEntity = createOrLoadHMTStatistics(); - statsEntity.totalBulkApprovalEventCount = - statsEntity.totalBulkApprovalEventCount.plus(ONE_BI); - statsEntity.save(); -} diff --git a/packages/sdk/typescript/subgraph/tests/hmt/fixtures.ts b/packages/sdk/typescript/subgraph/tests/hmt/fixtures.ts deleted file mode 100644 index 259a0ac3da..0000000000 --- a/packages/sdk/typescript/subgraph/tests/hmt/fixtures.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { newMockEvent } from 'matchstick-as/assembly/index'; -import { Address, ethereum, BigInt, dataSource } from '@graphprotocol/graph-ts'; - -import { - Transfer, - Approval, - BulkTransfer, - BulkApproval, -} from '../../generated/HMToken/HMToken'; -import { generateUniqueHash } from '../../tests/utils'; - -export function createTransferEvent( - from: string, - to: string, - value: i32, - timestamp: BigInt -): Transfer { - const transferEvent = changetype(newMockEvent()); - transferEvent.transaction.hash = generateUniqueHash( - to, - timestamp, - transferEvent.transaction.nonce - ); - - transferEvent.parameters = []; - transferEvent.transaction.from = Address.fromString(from); - transferEvent.transaction.to = Address.fromString( - dataSource.address().toHexString() - ); - transferEvent.block.timestamp = timestamp; - const fromParam = new ethereum.EventParam( - '_from', - ethereum.Value.fromAddress(Address.fromString(from)) - ); - const toParam = new ethereum.EventParam( - '_to', - ethereum.Value.fromAddress(Address.fromString(to)) - ); - const valueParam = new ethereum.EventParam( - '_value', - ethereum.Value.fromI32(value) - ); - - transferEvent.parameters.push(fromParam); - transferEvent.parameters.push(toParam); - transferEvent.parameters.push(valueParam); - - return transferEvent; -} - -export function createApprovalEvent( - spender: string, - owner: string, - value: i32, - timestamp: BigInt -): Approval { - const approvalEvent = changetype(newMockEvent()); - approvalEvent.transaction.hash = generateUniqueHash( - owner, - timestamp, - approvalEvent.transaction.nonce - ); - - approvalEvent.transaction.from = Address.fromString(spender); - approvalEvent.transaction.to = Address.fromString( - dataSource.address().toHexString() - ); - approvalEvent.parameters = []; - approvalEvent.block.timestamp = timestamp; - const ownerParam = new ethereum.EventParam( - '_spender', - ethereum.Value.fromAddress(Address.fromString(spender)) - ); - const spenderParam = new ethereum.EventParam( - '_owner', - ethereum.Value.fromAddress(Address.fromString(owner)) - ); - const valueParam = new ethereum.EventParam( - '_value', - ethereum.Value.fromI32(value) - ); - - approvalEvent.parameters.push(ownerParam); - approvalEvent.parameters.push(spenderParam); - approvalEvent.parameters.push(valueParam); - - return approvalEvent; -} - -export function createBulkTransferEvent( - txId: i32, - bulkCount: i32, - timestamp: BigInt -): BulkTransfer { - const bulkTransferEvent = changetype(newMockEvent()); - bulkTransferEvent.transaction.hash = generateUniqueHash( - bulkCount.toString(), - timestamp, - bulkTransferEvent.transaction.nonce - ); - bulkTransferEvent.transaction.to = Address.fromString( - dataSource.address().toHexString() - ); - - bulkTransferEvent.parameters = []; - bulkTransferEvent.block.timestamp = timestamp; - const txIdParam = new ethereum.EventParam( - '_txId', - ethereum.Value.fromI32(txId) - ); - const bulkCountParam = new ethereum.EventParam( - '_bulkCount', - ethereum.Value.fromI32(bulkCount) - ); - - bulkTransferEvent.parameters.push(txIdParam); - bulkTransferEvent.parameters.push(bulkCountParam); - - return bulkTransferEvent; -} - -export function createBulkApprovalEvent( - txId: i32, - bulkCount: i32, - timestamp: BigInt -): BulkApproval { - const bulkApprovalEvent = changetype(newMockEvent()); - bulkApprovalEvent.transaction.hash = generateUniqueHash( - bulkCount.toString(), - timestamp, - bulkApprovalEvent.transaction.nonce - ); - - bulkApprovalEvent.transaction.to = Address.fromString( - dataSource.address().toHexString() - ); - - bulkApprovalEvent.parameters = []; - bulkApprovalEvent.block.timestamp = timestamp; - const txIdParam = new ethereum.EventParam( - '_txId', - ethereum.Value.fromI32(txId) - ); - const bulkCountParam = new ethereum.EventParam( - '_bulkCount', - ethereum.Value.fromI32(bulkCount) - ); - - bulkApprovalEvent.parameters.push(txIdParam); - bulkApprovalEvent.parameters.push(bulkCountParam); - - return bulkApprovalEvent; -} diff --git a/packages/sdk/typescript/subgraph/tests/hmt/hmt.test.ts b/packages/sdk/typescript/subgraph/tests/hmt/hmt.test.ts deleted file mode 100644 index d8ac0bce0d..0000000000 --- a/packages/sdk/typescript/subgraph/tests/hmt/hmt.test.ts +++ /dev/null @@ -1,597 +0,0 @@ -import { Address, BigInt, DataSourceContext } from '@graphprotocol/graph-ts'; -import { - describe, - test, - assert, - clearStore, - dataSourceMock, - beforeAll, - afterAll, - beforeEach, -} from 'matchstick-as/assembly'; - -import { Escrow } from '../../generated/schema'; -import { - HMT_STATISTICS_ENTITY_ID, - handleTransfer, - handleBulkTransfer, - handleApproval, - handleBulkApproval, -} from '../../src/mapping/HMToken'; -import { toEventId } from '../../src/mapping/utils/event'; -import { ZERO_BI } from '../../src/mapping/utils/number'; -import { - createTransferEvent, - createBulkTransferEvent, - createApprovalEvent, - createBulkApprovalEvent, -} from './fixtures'; - -const tokenAddressString = '0xa16081f360e3847006db660bae1c6d1b2e17ffaa'; -const escrowAddressString = '0xa16081f360e3847006db660bae1c6d1b2e17ec2a'; -const escrowAddress = Address.fromString(escrowAddressString); -const operatorAddressString = '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266'; -const holderAddressString = '0x70997970c51812dc3a010c7d01b50e0d17dc79c8'; -const holderAddress = Address.fromString(holderAddressString); -const holder1AddressString = '0x92a2eEF7Ff696BCef98957a0189872680600a959'; -const holder1Address = Address.fromString(holder1AddressString); - -describe('HMToken', () => { - beforeAll(() => { - dataSourceMock.setReturnValues( - tokenAddressString, - 'rinkeby', - new DataSourceContext() - ); - - const escrow = new Escrow(escrowAddress); - escrow.address = escrowAddress; - escrow.token = Address.zero(); - escrow.factoryAddress = Address.zero(); - escrow.launcher = Address.zero(); - escrow.canceler = Address.zero(); - escrow.count = ZERO_BI; - escrow.balance = BigInt.fromI32(100); - escrow.totalFundedAmount = BigInt.fromI32(100); - escrow.amountPaid = ZERO_BI; - escrow.createdAt = BigInt.fromI32(0); - escrow.status = 'Launched'; - - escrow.save(); - }); - - afterAll(() => { - dataSourceMock.resetValues(); - }); - - describe('Transfer', () => { - test('Should properly handle Transfer event to Escrow', () => { - const transfer = createTransferEvent( - operatorAddressString, - escrowAddressString, - 1, - BigInt.fromI32(10) - ); - - handleTransfer(transfer); - - const id = toEventId(transfer).toHex(); - - // HMTTransferEvent - assert.fieldEquals( - 'HMTTransferEvent', - id, - 'block', - transfer.block.number.toString() - ); - assert.fieldEquals( - 'HMTTransferEvent', - id, - 'timestamp', - transfer.block.timestamp.toString() - ); - assert.fieldEquals( - 'HMTTransferEvent', - id, - 'txHash', - transfer.transaction.hash.toHex() - ); - assert.fieldEquals('HMTTransferEvent', id, 'from', operatorAddressString); - assert.fieldEquals('HMTTransferEvent', id, 'to', escrowAddressString); - assert.fieldEquals('HMTTransferEvent', id, 'amount', '1'); - - // Escrow - assert.fieldEquals('Escrow', escrowAddressString, 'balance', '101'); - assert.fieldEquals( - 'Escrow', - escrowAddressString, - 'totalFundedAmount', - '101' - ); - assert.fieldEquals( - 'Transaction', - transfer.transaction.hash.toHex(), - 'txHash', - transfer.transaction.hash.toHex() - ); - assert.fieldEquals( - 'Transaction', - transfer.transaction.hash.toHex(), - 'method', - 'fund' - ); - assert.fieldEquals( - 'Transaction', - transfer.transaction.hash.toHex(), - 'block', - transfer.block.number.toString() - ); - assert.fieldEquals( - 'Transaction', - transfer.transaction.hash.toHex(), - 'from', - operatorAddressString - ); - assert.fieldEquals( - 'Transaction', - transfer.transaction.hash.toHex(), - 'to', - tokenAddressString - ); - assert.fieldEquals( - 'Transaction', - transfer.transaction.hash.toHex(), - 'receiver', - escrowAddressString - ); - assert.fieldEquals( - 'Transaction', - transfer.transaction.hash.toHex(), - 'escrow', - escrowAddressString - ); - assert.fieldEquals( - 'Transaction', - transfer.transaction.hash.toHex(), - 'token', - tokenAddressString - ); - assert.fieldEquals( - 'Transaction', - transfer.transaction.hash.toHex(), - 'value', - '1' - ); - }); - - test('Should properly handle Transfer event to Holder', () => { - const transfer = createTransferEvent( - operatorAddressString, - holderAddressString, - 1, - BigInt.fromI32(20) - ); - - handleTransfer(transfer); - - const id = toEventId(transfer).toHex(); - - // HMTTransferEvent - assert.fieldEquals( - 'HMTTransferEvent', - id, - 'block', - transfer.block.number.toString() - ); - assert.fieldEquals( - 'HMTTransferEvent', - id, - 'timestamp', - transfer.block.timestamp.toString() - ); - assert.fieldEquals( - 'HMTTransferEvent', - id, - 'txHash', - transfer.transaction.hash.toHex() - ); - assert.fieldEquals('HMTTransferEvent', id, 'from', operatorAddressString); - assert.fieldEquals('HMTTransferEvent', id, 'to', holderAddressString); - assert.fieldEquals('HMTTransferEvent', id, 'amount', '1'); - - // Holder - assert.fieldEquals( - 'Holder', - holderAddressString, - 'address', - holderAddressString - ); - assert.fieldEquals('Holder', holderAddressString, 'balance', '1'); - assert.fieldEquals( - 'Transaction', - transfer.transaction.hash.toHex(), - 'txHash', - transfer.transaction.hash.toHex() - ); - assert.fieldEquals( - 'Transaction', - transfer.transaction.hash.toHex(), - 'method', - 'transfer' - ); - assert.fieldEquals( - 'Transaction', - transfer.transaction.hash.toHex(), - 'block', - transfer.block.number.toString() - ); - assert.fieldEquals( - 'Transaction', - transfer.transaction.hash.toHex(), - 'from', - operatorAddressString - ); - assert.fieldEquals( - 'Transaction', - transfer.transaction.hash.toHex(), - 'to', - tokenAddressString - ); - assert.fieldEquals( - 'Transaction', - transfer.transaction.hash.toHex(), - 'receiver', - holderAddressString - ); - assert.fieldEquals( - 'Transaction', - transfer.transaction.hash.toHex(), - 'token', - tokenAddressString - ); - assert.fieldEquals( - 'Transaction', - transfer.transaction.hash.toHex(), - 'value', - '1' - ); - }); - }); - - test('Should properly handle BulkTransfer event', () => { - const bulkTransfer = createBulkTransferEvent(1, 3, BigInt.fromI32(10)); - - handleBulkTransfer(bulkTransfer); - - const id = toEventId(bulkTransfer).toHex(); - - // HMTBulkTransferEvent - assert.fieldEquals( - 'HMTBulkTransferEvent', - id, - 'block', - bulkTransfer.block.number.toString() - ); - assert.fieldEquals( - 'HMTBulkTransferEvent', - id, - 'timestamp', - bulkTransfer.block.timestamp.toString() - ); - assert.fieldEquals( - 'HMTBulkTransferEvent', - id, - 'txHash', - bulkTransfer.transaction.hash.toHex() - ); - assert.fieldEquals('HMTBulkTransferEvent', id, 'txId', '1'); - assert.fieldEquals('HMTBulkTransferEvent', id, 'bulkCount', '3'); - assert.fieldEquals( - 'Transaction', - bulkTransfer.transaction.hash.toHex(), - 'txHash', - bulkTransfer.transaction.hash.toHex() - ); - assert.fieldEquals( - 'Transaction', - bulkTransfer.transaction.hash.toHex(), - 'method', - 'transferBulk' - ); - assert.fieldEquals( - 'Transaction', - bulkTransfer.transaction.hash.toHex(), - 'block', - bulkTransfer.block.number.toString() - ); - assert.fieldEquals( - 'Transaction', - bulkTransfer.transaction.hash.toHex(), - 'from', - bulkTransfer.transaction.from.toHex() - ); - }); - - test('Should properly handle Approval event', () => { - const approval = createApprovalEvent( - holderAddressString, - operatorAddressString, - 1, - BigInt.fromI32(100) - ); - - handleApproval(approval); - - const id = toEventId(approval).toHex(); - - // HMTApprovalEvent - assert.fieldEquals( - 'HMTApprovalEvent', - id, - 'block', - approval.block.number.toString() - ); - assert.fieldEquals( - 'HMTApprovalEvent', - id, - 'timestamp', - approval.block.timestamp.toString() - ); - assert.fieldEquals( - 'HMTApprovalEvent', - id, - 'txHash', - approval.transaction.hash.toHex() - ); - assert.fieldEquals('HMTApprovalEvent', id, 'owner', holderAddressString); - assert.fieldEquals( - 'HMTApprovalEvent', - id, - 'spender', - operatorAddressString - ); - assert.fieldEquals('HMTApprovalEvent', id, 'amount', '1'); - assert.fieldEquals( - 'Transaction', - approval.transaction.hash.toHex(), - 'txHash', - approval.transaction.hash.toHex() - ); - assert.fieldEquals( - 'Transaction', - approval.transaction.hash.toHex(), - 'method', - 'approve' - ); - assert.fieldEquals( - 'Transaction', - approval.transaction.hash.toHex(), - 'block', - approval.block.number.toString() - ); - assert.fieldEquals( - 'Transaction', - approval.transaction.hash.toHex(), - 'from', - approval.transaction.from.toHex() - ); - assert.fieldEquals( - 'Transaction', - approval.transaction.hash.toHex(), - 'value', - '1' - ); - }); - - test('Should properly handle BulkApproval event', () => { - const bulkApproval = createBulkApprovalEvent(1, 3, BigInt.fromI32(200)); - - handleBulkApproval(bulkApproval); - - const id = toEventId(bulkApproval).toHex(); - - // HMTBulkApprovalEvent - assert.fieldEquals( - 'HMTBulkApprovalEvent', - id, - 'block', - bulkApproval.block.number.toString() - ); - assert.fieldEquals( - 'HMTBulkApprovalEvent', - id, - 'timestamp', - bulkApproval.block.timestamp.toString() - ); - assert.fieldEquals( - 'HMTBulkApprovalEvent', - id, - 'txHash', - bulkApproval.transaction.hash.toHex() - ); - assert.fieldEquals('HMTBulkApprovalEvent', id, 'txId', '1'); - assert.fieldEquals('HMTBulkApprovalEvent', id, 'bulkCount', '3'); - assert.fieldEquals( - 'Transaction', - bulkApproval.transaction.hash.toHex(), - 'txHash', - bulkApproval.transaction.hash.toHex() - ); - assert.fieldEquals( - 'Transaction', - bulkApproval.transaction.hash.toHex(), - 'method', - 'increaseApprovalBulk' - ); - assert.fieldEquals( - 'Transaction', - bulkApproval.transaction.hash.toHex(), - 'block', - bulkApproval.block.number.toString() - ); - assert.fieldEquals( - 'Transaction', - bulkApproval.transaction.hash.toHex(), - 'from', - bulkApproval.transaction.from.toHex() - ); - }); - - describe('Statistics', () => { - beforeEach(() => { - clearStore(); - }); - - test('Should properly calculate Transfer event in statistics', () => { - const transfer1 = createTransferEvent( - '0xD979105297fB0eee83F7433fC09279cb5B94fFC6', - '0x92a2eEF7Ff696BCef98957a0189872680600a959', - 1, - BigInt.fromI32(10) - ); - const transfer2 = createTransferEvent( - '0xD979105297fB0eee83F7433fC09279cb5B94fFC6', - '0x92a2eEF7Ff696BCef98957a0189872680600a959', - 2, - BigInt.fromI32(10) - ); - - handleTransfer(transfer1); - handleTransfer(transfer2); - - assert.fieldEquals( - 'HMTokenStatistics', - HMT_STATISTICS_ENTITY_ID.toHex(), - 'totalTransferEventCount', - '2' - ); - assert.fieldEquals( - 'HMTokenStatistics', - HMT_STATISTICS_ENTITY_ID.toHex(), - 'totalValueTransfered', - '3' - ); - }); - - test('Should properly calculate holders in statistics', () => { - const transfer1 = createTransferEvent( - '0x0000000000000000000000000000000000000000', - holderAddressString, - 10, - BigInt.fromI32(10) - ); - - const transfer2 = createTransferEvent( - holderAddressString, - holder1AddressString, - 0, - BigInt.fromI32(10) - ); - - handleTransfer(transfer1); - handleTransfer(transfer2); - - assert.fieldEquals('Holder', holderAddress.toHex(), 'balance', '10'); - assert.fieldEquals('Holder', holder1Address.toHex(), 'balance', '0'); - - assert.fieldEquals( - 'HMTokenStatistics', - HMT_STATISTICS_ENTITY_ID.toHex(), - 'holders', - '1' - ); - - const transfer3 = createTransferEvent( - '0x0000000000000000000000000000000000000000', - holder1AddressString, - 10, - BigInt.fromI32(10) - ); - - handleTransfer(transfer3); - - assert.fieldEquals('Holder', holderAddress.toHex(), 'balance', '10'); - assert.fieldEquals('Holder', holder1Address.toHex(), 'balance', '10'); - assert.fieldEquals( - 'HMTokenStatistics', - HMT_STATISTICS_ENTITY_ID.toHex(), - 'holders', - '2' - ); - - const transfer4 = createTransferEvent( - holderAddressString, - holder1AddressString, - 10, - BigInt.fromI32(10) - ); - - handleTransfer(transfer4); - - assert.fieldEquals('Holder', holderAddress.toHex(), 'balance', '0'); - assert.fieldEquals('Holder', holder1Address.toHex(), 'balance', '20'); - assert.fieldEquals( - 'HMTokenStatistics', - HMT_STATISTICS_ENTITY_ID.toHex(), - 'holders', - '1' - ); - }); - - test('Should properly calculate BulkTransfer event in statistics', () => { - const bulkTransfer1 = createBulkTransferEvent(1, 3, BigInt.fromI32(10)); - const bulkTransfer2 = createBulkTransferEvent(2, 3, BigInt.fromI32(10)); - - handleBulkTransfer(bulkTransfer1); - handleBulkTransfer(bulkTransfer2); - - assert.fieldEquals( - 'HMTokenStatistics', - HMT_STATISTICS_ENTITY_ID.toHex(), - 'totalBulkTransferEventCount', - '2' - ); - }); - - test('Should properly calculate Approval event in statistics', () => { - const approval1 = createApprovalEvent( - '0x92a2eEF7Ff696BCef98957a0189872680600a959', - '0xD979105297fB0eee83F7433fC09279cb5B94fFC6', - 10, - BigInt.fromI32(10) - ); - - const approval2 = createApprovalEvent( - '0x92a2eEF7Ff696BCef98957a0189872680600a959', - '0xD979105297fB0eee83F7433fC09279cb5B94fFC6', - 10, - BigInt.fromI32(10) - ); - - handleApproval(approval1); - handleApproval(approval2); - - assert.fieldEquals( - 'HMTokenStatistics', - HMT_STATISTICS_ENTITY_ID.toHex(), - 'totalApprovalEventCount', - '2' - ); - }); - - test('Should properly calculate BulkApproval event in statistics', () => { - const bulkApproval1 = createBulkApprovalEvent(1, 3, BigInt.fromI32(10)); - const bulkApproval2 = createBulkApprovalEvent(2, 3, BigInt.fromI32(10)); - - handleBulkApproval(bulkApproval1); - handleBulkApproval(bulkApproval2); - - assert.fieldEquals( - 'HMTokenStatistics', - HMT_STATISTICS_ENTITY_ID.toHex(), - 'totalBulkApprovalEventCount', - '2' - ); - }); - }); -}); diff --git a/packages/subgraph/hmt/.gitignore b/packages/subgraph/hmt/.gitignore new file mode 100644 index 0000000000..814a4b37a2 --- /dev/null +++ b/packages/subgraph/hmt/.gitignore @@ -0,0 +1,12 @@ +# Graph build +build/ + +# Graph generated +generated/ + +# Mustache generated +subgraph.yaml + +# Graph test generated +tests/.bin +tests/.latest.json diff --git a/packages/sdk/typescript/subgraph/.prettierignore b/packages/subgraph/hmt/.prettierignore similarity index 100% rename from packages/sdk/typescript/subgraph/.prettierignore rename to packages/subgraph/hmt/.prettierignore diff --git a/packages/sdk/typescript/subgraph/.prettierrc b/packages/subgraph/hmt/.prettierrc similarity index 100% rename from packages/sdk/typescript/subgraph/.prettierrc rename to packages/subgraph/hmt/.prettierrc diff --git a/packages/subgraph/hmt/README.md b/packages/subgraph/hmt/README.md new file mode 100644 index 0000000000..8c9d054dca --- /dev/null +++ b/packages/subgraph/hmt/README.md @@ -0,0 +1,66 @@ +

+ Human Protocol +

+ +

Human HMT statistics subgraph

+

+Dedicated subgraph to index HMToken statistics only. +

+ +## Installation + +Package installation: + +```bash +yarn install && yarn workspace human-protocol build:core +``` + +## Development + +Generate and build artifacts for a network: + +```bash +NETWORK=polygon yarn workspace @tools/subgraph-hmt-stats generate +NETWORK=polygon yarn workspace @tools/subgraph-hmt-stats build +``` + +### Tests + +To run subgraph tests: + +```bash +NETWORK=polygon yarn workspace @tools/subgraph-hmt-stats generate +yarn workspace @tools/subgraph-hmt-stats test +``` + +This subgraph does not use staking/escrow templates. + +## Indexed entities + +- `HMTokenStatistics` +- `Holder` +- `UniqueSender` +- `UniqueReceiver` +- `EventDayData` (HMT daily fields only) + +## Supported networks + +- Ethereum Mainnet +- Sepolia (testnet) +- BSC Mainnet +- BSC Testnet (testnet) +- Polygon Mainnet +- Polygon Amoy (testnet) +- Localhost + +## Add a new network + +1. Add network configuration as `config/NETWORK.json`. +2. Ensure required fields are present: + - `network` + - `description` + - `HMToken.address` + - `HMToken.startBlock` + - `HMToken.abi` +3. Generate artifacts: `NETWORK=NETWORK yarn workspace @tools/subgraph-hmt-stats generate`. +4. Build subgraph: `yarn workspace @tools/subgraph-hmt-stats build`. diff --git a/packages/subgraph/hmt/config/amoy.json b/packages/subgraph/hmt/config/amoy.json new file mode 100644 index 0000000000..9e3ff34fee --- /dev/null +++ b/packages/subgraph/hmt/config/amoy.json @@ -0,0 +1,9 @@ +{ + "network": "polygon-amoy", + "description": "HUMAN subgraph on Amoy Testnet", + "HMToken": { + "address": "0x792abbcC99c01dbDec49c9fa9A828a186Da45C33", + "startBlock": 5769546, + "abi": "../../../node_modules/@human-protocol/core/abis/HMToken.json" + } +} diff --git a/packages/subgraph/hmt/config/bsc-testnet.json b/packages/subgraph/hmt/config/bsc-testnet.json new file mode 100644 index 0000000000..bab01016d0 --- /dev/null +++ b/packages/subgraph/hmt/config/bsc-testnet.json @@ -0,0 +1,9 @@ +{ + "network": "chapel", + "description": "Human subgraph on Binance Smart Chain testnet", + "HMToken": { + "address": "0xE3D74BBFa45B4bCa69FF28891fBE392f4B4d4e4d", + "startBlock": 26716354, + "abi": "../../../node_modules/@human-protocol/core/abis/HMToken.json" + } +} diff --git a/packages/subgraph/hmt/config/bsc.json b/packages/subgraph/hmt/config/bsc.json new file mode 100644 index 0000000000..eab6ad0b64 --- /dev/null +++ b/packages/subgraph/hmt/config/bsc.json @@ -0,0 +1,9 @@ +{ + "network": "bsc", + "description": "Human subgraph on BSC mainnet", + "HMToken": { + "address": "0x711Fd6ab6d65A98904522d4e3586F492B989c527", + "startBlock": 25383172, + "abi": "../../../node_modules/@human-protocol/core/abis/HMToken.json" + } +} diff --git a/packages/subgraph/hmt/config/ethereum.json b/packages/subgraph/hmt/config/ethereum.json new file mode 100644 index 0000000000..9004bd2aca --- /dev/null +++ b/packages/subgraph/hmt/config/ethereum.json @@ -0,0 +1,9 @@ +{ + "network": "mainnet", + "description": "Human subgraph on Ethereum Mainnet", + "HMToken": { + "address": "0xd1ba9BAC957322D6e8c07a160a3A8dA11A0d2867", + "startBlock": 12184475, + "abi": "../../../node_modules/@human-protocol/core/abis/HMToken.json" + } +} diff --git a/packages/subgraph/hmt/config/localhost.json b/packages/subgraph/hmt/config/localhost.json new file mode 100644 index 0000000000..e69584771f --- /dev/null +++ b/packages/subgraph/hmt/config/localhost.json @@ -0,0 +1,9 @@ +{ + "network": "localhost", + "description": "Human subgraph on localhost", + "HMToken": { + "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "startBlock": 1, + "abi": "../../../node_modules/@human-protocol/core/abis/HMToken.json" + } +} diff --git a/packages/subgraph/hmt/config/polygon.json b/packages/subgraph/hmt/config/polygon.json new file mode 100644 index 0000000000..ee304360a6 --- /dev/null +++ b/packages/subgraph/hmt/config/polygon.json @@ -0,0 +1,9 @@ +{ + "network": "matic", + "description": "Human subgraph on Polygon mainnet", + "HMToken": { + "address": "0xc748B2A084F8eFc47E086ccdDD9b7e67aEb571Bf", + "startBlock": 20181701, + "abi": "../../../node_modules/@human-protocol/core/abis/HMToken.json" + } +} diff --git a/packages/subgraph/hmt/config/sepolia.json b/packages/subgraph/hmt/config/sepolia.json new file mode 100644 index 0000000000..ebc16b0e25 --- /dev/null +++ b/packages/subgraph/hmt/config/sepolia.json @@ -0,0 +1,9 @@ +{ + "network": "sepolia", + "description": "HUMAN subgraph on Sepolia Ethereum Testnet", + "HMToken": { + "address": "0x792abbcC99c01dbDec49c9fa9A828a186Da45C33", + "startBlock": 5716225, + "abi": "../../../node_modules/@human-protocol/core/abis/HMToken.json" + } +} diff --git a/packages/sdk/typescript/subgraph/eslint.config.mjs b/packages/subgraph/hmt/eslint.config.mjs similarity index 100% rename from packages/sdk/typescript/subgraph/eslint.config.mjs rename to packages/subgraph/hmt/eslint.config.mjs diff --git a/packages/subgraph/hmt/matchstick.yaml b/packages/subgraph/hmt/matchstick.yaml new file mode 100644 index 0000000000..be49651fb8 --- /dev/null +++ b/packages/subgraph/hmt/matchstick.yaml @@ -0,0 +1 @@ +libsFolder: ../../../node_modules diff --git a/packages/subgraph/hmt/package.json b/packages/subgraph/hmt/package.json new file mode 100644 index 0000000000..ceb9a600b1 --- /dev/null +++ b/packages/subgraph/hmt/package.json @@ -0,0 +1,53 @@ +{ + "name": "@tools/subgraph-hmt-stats", + "private": true, + "description": "Human Protocol HMT Statistics Subgraph", + "version": "1.0.0", + "files": [ + "generated" + ], + "scripts": { + "clean": "rm -rf build generated subgraph.yaml", + "generate": "mustache ./config/$NETWORK.json template.yaml > subgraph.yaml && graph codegen", + "codegen": "graph codegen", + "build": "graph build", + "test": "NETWORK=polygon yarn generate && graph test", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "format": "prettier --write '**/*.{ts,json,graphql,yaml}'" + }, + "repository": { + "type": "git", + "url": "https://github.com/humanprotocol/human-protocol.git", + "directory": "packages/sdk/typescript/subgraph/hmt" + }, + "keywords": [ + "human-protocol", + "sdk", + "subgraph", + "hmt", + "statistics" + ], + "license": "MIT", + "devDependencies": { + "@graphprotocol/graph-cli": "^0.97.1", + "@graphprotocol/graph-ts": "^0.38.0", + "@graphql-eslint/eslint-plugin": "^3.19.1", + "@human-protocol/core": "workspace:*", + "eslint": "^9.39.1", + "ethers": "~6.15.0", + "graphql": "^16.6.0", + "matchstick-as": "^0.6.0", + "mustache": "^4.2.0", + "prettier": "^3.8.1" + }, + "lint-staged": { + "*.{ts,graphql}": [ + "prettier --write", + "eslint --fix" + ], + "*.{yaml,json}": [ + "prettier --write" + ] + } +} diff --git a/packages/subgraph/hmt/schema.graphql b/packages/subgraph/hmt/schema.graphql new file mode 100644 index 0000000000..75ae97773a --- /dev/null +++ b/packages/subgraph/hmt/schema.graphql @@ -0,0 +1,38 @@ +type HMTokenStatistics @entity(immutable: false) { + id: Bytes! + totalTransferEventCount: BigInt! + totalBulkTransferEventCount: BigInt! + totalApprovalEventCount: BigInt! + totalBulkApprovalEventCount: BigInt! + totalValueTransfered: BigInt! + holders: BigInt! +} + +type Holder @entity(immutable: false) { + id: Bytes! + address: Bytes! + balance: BigInt! +} + +type UniqueSender @entity(immutable: false) { + id: Bytes! + address: Bytes! + transferCount: BigInt! + timestamp: BigInt! +} + +type UniqueReceiver @entity(immutable: false) { + id: Bytes! + address: Bytes! + receiveCount: BigInt! + timestamp: BigInt! +} + +type EventDayData @entity(immutable: false) { + id: Bytes! + timestamp: Int! + dailyHMTTransferCount: BigInt! + dailyHMTTransferAmount: BigInt! + dailyUniqueSenders: BigInt! + dailyUniqueReceivers: BigInt! +} diff --git a/packages/subgraph/hmt/src/mapping/HMToken.ts b/packages/subgraph/hmt/src/mapping/HMToken.ts new file mode 100644 index 0000000000..e94ce96287 --- /dev/null +++ b/packages/subgraph/hmt/src/mapping/HMToken.ts @@ -0,0 +1,203 @@ +import { Address, BigInt, Bytes } from '@graphprotocol/graph-ts'; + +import { + Approval, + BulkApproval, + BulkTransfer, + Transfer, +} from '../../generated/HMToken/HMToken'; +import { + HMTokenStatistics, + Holder, + UniqueReceiver, + UniqueSender, +} from '../../generated/schema'; +import { getEventDayData } from './utils/dayUpdates'; +import { ONE_BI, ONE_DAY, ZERO_BI } from './utils/number'; + +export const HMT_STATISTICS_ENTITY_ID = Bytes.fromUTF8('hmt-statistics-id'); +const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; + +function constructStatsEntity(): HMTokenStatistics { + const entity = new HMTokenStatistics(HMT_STATISTICS_ENTITY_ID); + + entity.totalTransferEventCount = ZERO_BI; + entity.totalApprovalEventCount = ZERO_BI; + entity.totalBulkApprovalEventCount = ZERO_BI; + entity.totalBulkTransferEventCount = ZERO_BI; + entity.totalValueTransfered = ZERO_BI; + entity.holders = ZERO_BI; + + return entity; +} + +function createOrLoadUniqueSender( + dayStartTimestamp: BigInt, + timestamp: BigInt, + address: Address +): UniqueSender { + const id = Bytes.fromI32(dayStartTimestamp.toI32()).concat(address); + let uniqueSender = UniqueSender.load(id); + + if (!uniqueSender) { + uniqueSender = new UniqueSender(id); + uniqueSender.address = address; + uniqueSender.transferCount = ZERO_BI; + uniqueSender.timestamp = timestamp; + uniqueSender.save(); + } + + return uniqueSender; +} + +function createOrLoadUniqueReceiver( + dayStartTimestamp: BigInt, + timestamp: BigInt, + address: Address +): UniqueReceiver { + const id = Bytes.fromI32(dayStartTimestamp.toI32()).concat(address); + let uniqueReceiver = UniqueReceiver.load(id); + + if (!uniqueReceiver) { + uniqueReceiver = new UniqueReceiver(id); + uniqueReceiver.address = address; + uniqueReceiver.receiveCount = ZERO_BI; + uniqueReceiver.timestamp = timestamp; + uniqueReceiver.save(); + } + + return uniqueReceiver; +} + +function updateHolders( + holderAddress: Address, + value: BigInt, + increase: boolean +): BigInt { + if (holderAddress.toHexString() == ZERO_ADDRESS) { + return ZERO_BI; + } + + let count = ZERO_BI; + let holder = Holder.load(holderAddress); + + if (holder == null) { + holder = new Holder(holderAddress); + holder.address = holderAddress; + holder.balance = ZERO_BI; + } + + const balanceBeforeTransfer = holder.balance; + holder.balance = increase + ? holder.balance.plus(value) + : holder.balance.minus(value); + + if (balanceBeforeTransfer.isZero() && !holder.balance.isZero()) { + count = ONE_BI; + } else if (!balanceBeforeTransfer.isZero() && holder.balance.isZero()) { + count = ONE_BI.neg(); + } + + holder.save(); + + return count; +} + +function createOrLoadHMTStatistics(): HMTokenStatistics { + let statsEntity = HMTokenStatistics.load(HMT_STATISTICS_ENTITY_ID); + + if (!statsEntity) { + statsEntity = constructStatsEntity(); + } + + return statsEntity; +} + +export function handleTransfer(event: Transfer): void { + const statsEntity = createOrLoadHMTStatistics(); + statsEntity.totalTransferEventCount = + statsEntity.totalTransferEventCount.plus(ONE_BI); + statsEntity.totalValueTransfered = statsEntity.totalValueTransfered.plus( + event.params._value + ); + + const diffHolders = updateHolders( + event.params._from, + event.params._value, + false + ).plus(updateHolders(event.params._to, event.params._value, true)); + statsEntity.holders = statsEntity.holders.plus(diffHolders); + + const eventDayData = getEventDayData(event); + eventDayData.dailyHMTTransferCount = + eventDayData.dailyHMTTransferCount.plus(ONE_BI); + eventDayData.dailyHMTTransferAmount = + eventDayData.dailyHMTTransferAmount.plus(event.params._value); + + const timestamp = event.block.timestamp.toI32(); + const dayID = timestamp / ONE_DAY; + const dayStartTimestamp = BigInt.fromI32(dayID * ONE_DAY); + + if (event.params._from.toHexString() != ZERO_ADDRESS) { + const uniqueSender = createOrLoadUniqueSender( + dayStartTimestamp, + event.block.timestamp, + event.params._from + ); + + if (uniqueSender.transferCount.equals(ZERO_BI)) { + eventDayData.dailyUniqueSenders = + eventDayData.dailyUniqueSenders.plus(ONE_BI); + } + + uniqueSender.transferCount = uniqueSender.transferCount.plus( + event.params._value + ); + uniqueSender.save(); + } + + if (event.params._to.toHexString() != ZERO_ADDRESS) { + const uniqueReceiver = createOrLoadUniqueReceiver( + dayStartTimestamp, + event.block.timestamp, + event.params._to + ); + + if (uniqueReceiver.receiveCount.equals(ZERO_BI)) { + eventDayData.dailyUniqueReceivers = + eventDayData.dailyUniqueReceivers.plus(ONE_BI); + } + + uniqueReceiver.receiveCount = uniqueReceiver.receiveCount.plus( + event.params._value + ); + uniqueReceiver.save(); + } + + eventDayData.save(); + statsEntity.save(); +} + +export function handleBulkTransfer(event: BulkTransfer): void { + void event; + const statsEntity = createOrLoadHMTStatistics(); + statsEntity.totalBulkTransferEventCount = + statsEntity.totalBulkTransferEventCount.plus(ONE_BI); + statsEntity.save(); +} + +export function handleApproval(event: Approval): void { + void event; + const statsEntity = createOrLoadHMTStatistics(); + statsEntity.totalApprovalEventCount = + statsEntity.totalApprovalEventCount.plus(ONE_BI); + statsEntity.save(); +} + +export function handleBulkApproval(event: BulkApproval): void { + void event; + const statsEntity = createOrLoadHMTStatistics(); + statsEntity.totalBulkApprovalEventCount = + statsEntity.totalBulkApprovalEventCount.plus(ONE_BI); + statsEntity.save(); +} diff --git a/packages/subgraph/hmt/src/mapping/utils/dayUpdates.ts b/packages/subgraph/hmt/src/mapping/utils/dayUpdates.ts new file mode 100644 index 0000000000..2706271424 --- /dev/null +++ b/packages/subgraph/hmt/src/mapping/utils/dayUpdates.ts @@ -0,0 +1,24 @@ +import { Bytes, ethereum } from '@graphprotocol/graph-ts'; +import { EventDayData } from '../../../generated/schema'; +import { ONE_DAY, ZERO_BI } from './number'; + +export function getEventDayData(event: ethereum.Event): EventDayData { + const timestamp = event.block.timestamp.toI32(); + const dayID = timestamp / ONE_DAY; + const dayStartTimestamp = dayID * ONE_DAY; + + const id = Bytes.fromI32(dayID); + let eventDayData = EventDayData.load(id); + + if (eventDayData == null) { + eventDayData = new EventDayData(id); + eventDayData.timestamp = dayStartTimestamp; + eventDayData.dailyHMTTransferCount = ZERO_BI; + eventDayData.dailyHMTTransferAmount = ZERO_BI; + eventDayData.dailyUniqueSenders = ZERO_BI; + eventDayData.dailyUniqueReceivers = ZERO_BI; + eventDayData.save(); + } + + return eventDayData; +} diff --git a/packages/sdk/typescript/subgraph/src/mapping/utils/number.ts b/packages/subgraph/hmt/src/mapping/utils/number.ts similarity index 100% rename from packages/sdk/typescript/subgraph/src/mapping/utils/number.ts rename to packages/subgraph/hmt/src/mapping/utils/number.ts diff --git a/packages/subgraph/hmt/template.yaml b/packages/subgraph/hmt/template.yaml new file mode 100644 index 0000000000..ae4cb544ab --- /dev/null +++ b/packages/subgraph/hmt/template.yaml @@ -0,0 +1,37 @@ +specVersion: 1.0.0 +description: '{{ description }}' +schema: + file: ./schema.graphql +indexerHints: + prune: auto +dataSources: + - kind: ethereum + name: HMToken + network: '{{ network }}' + source: + abi: HMToken + address: '{{ HMToken.address }}' + startBlock: {{ HMToken.startBlock }} + mapping: + kind: ethereum/events + apiVersion: 0.0.7 + language: wasm/assemblyscript + entities: + - HMTokenStatistics + - Holder + - EventDayData + - UniqueSender + - UniqueReceiver + abis: + - name: HMToken + file: '{{{ HMToken.abi }}}' + eventHandlers: + - event: Approval(indexed address,indexed address,uint256) + handler: handleApproval + - event: BulkApproval(indexed uint256,uint256) + handler: handleBulkApproval + - event: BulkTransfer(indexed uint256,uint256) + handler: handleBulkTransfer + - event: Transfer(indexed address,indexed address,uint256) + handler: handleTransfer + file: ./src/mapping/HMToken.ts diff --git a/packages/subgraph/hmt/tests/hmt/fixtures.ts b/packages/subgraph/hmt/tests/hmt/fixtures.ts new file mode 100644 index 0000000000..11f8ce43dd --- /dev/null +++ b/packages/subgraph/hmt/tests/hmt/fixtures.ts @@ -0,0 +1,138 @@ +import { newMockEvent } from 'matchstick-as/assembly/index'; +import { Address, BigInt, ethereum } from '@graphprotocol/graph-ts'; + +import { + Transfer, + Approval, + BulkTransfer, + BulkApproval, +} from '../../generated/HMToken/HMToken'; +import { generateUniqueHash } from '../../tests/utils'; + +const TOKEN_ADDRESS = '0xa16081f360e3847006db660bae1c6d1b2e17ffaa'; + +export function createTransferEvent( + from: string, + to: string, + value: i32, + timestamp: BigInt +): Transfer { + const transferEvent = changetype(newMockEvent()); + transferEvent.transaction.hash = generateUniqueHash( + to, + timestamp, + transferEvent.transaction.nonce + ); + transferEvent.transaction.from = Address.fromString(from); + transferEvent.transaction.to = Address.fromString(TOKEN_ADDRESS); + transferEvent.block.timestamp = timestamp; + transferEvent.block.number = timestamp; + + transferEvent.parameters = []; + transferEvent.parameters.push( + new ethereum.EventParam( + '_from', + ethereum.Value.fromAddress(Address.fromString(from)) + ) + ); + transferEvent.parameters.push( + new ethereum.EventParam( + '_to', + ethereum.Value.fromAddress(Address.fromString(to)) + ) + ); + transferEvent.parameters.push( + new ethereum.EventParam('_value', ethereum.Value.fromI32(value)) + ); + + return transferEvent; +} + +export function createApprovalEvent( + owner: string, + spender: string, + value: i32, + timestamp: BigInt +): Approval { + const approvalEvent = changetype(newMockEvent()); + approvalEvent.transaction.hash = generateUniqueHash( + owner, + timestamp, + approvalEvent.transaction.nonce + ); + approvalEvent.transaction.from = Address.fromString(owner); + approvalEvent.transaction.to = Address.fromString(TOKEN_ADDRESS); + approvalEvent.block.timestamp = timestamp; + approvalEvent.block.number = timestamp; + + approvalEvent.parameters = []; + approvalEvent.parameters.push( + new ethereum.EventParam( + '_owner', + ethereum.Value.fromAddress(Address.fromString(owner)) + ) + ); + approvalEvent.parameters.push( + new ethereum.EventParam( + '_spender', + ethereum.Value.fromAddress(Address.fromString(spender)) + ) + ); + approvalEvent.parameters.push( + new ethereum.EventParam('_value', ethereum.Value.fromI32(value)) + ); + + return approvalEvent; +} + +export function createBulkTransferEvent( + txId: i32, + bulkCount: i32, + timestamp: BigInt +): BulkTransfer { + const bulkTransferEvent = changetype(newMockEvent()); + bulkTransferEvent.transaction.hash = generateUniqueHash( + txId.toString(), + timestamp, + bulkTransferEvent.transaction.nonce + ); + bulkTransferEvent.transaction.to = Address.fromString(TOKEN_ADDRESS); + bulkTransferEvent.block.timestamp = timestamp; + bulkTransferEvent.block.number = timestamp; + + bulkTransferEvent.parameters = []; + bulkTransferEvent.parameters.push( + new ethereum.EventParam('_txId', ethereum.Value.fromI32(txId)) + ); + bulkTransferEvent.parameters.push( + new ethereum.EventParam('_bulkCount', ethereum.Value.fromI32(bulkCount)) + ); + + return bulkTransferEvent; +} + +export function createBulkApprovalEvent( + txId: i32, + bulkCount: i32, + timestamp: BigInt +): BulkApproval { + const bulkApprovalEvent = changetype(newMockEvent()); + bulkApprovalEvent.transaction.hash = generateUniqueHash( + txId.toString(), + timestamp, + bulkApprovalEvent.transaction.nonce + ); + bulkApprovalEvent.transaction.to = Address.fromString(TOKEN_ADDRESS); + bulkApprovalEvent.block.timestamp = timestamp; + bulkApprovalEvent.block.number = timestamp; + + bulkApprovalEvent.parameters = []; + bulkApprovalEvent.parameters.push( + new ethereum.EventParam('_txId', ethereum.Value.fromI32(txId)) + ); + bulkApprovalEvent.parameters.push( + new ethereum.EventParam('_bulkCount', ethereum.Value.fromI32(bulkCount)) + ); + + return bulkApprovalEvent; +} diff --git a/packages/subgraph/hmt/tests/hmt/hmt.test.ts b/packages/subgraph/hmt/tests/hmt/hmt.test.ts new file mode 100644 index 0000000000..92ab93fb4d --- /dev/null +++ b/packages/subgraph/hmt/tests/hmt/hmt.test.ts @@ -0,0 +1,302 @@ +import { Address, BigInt, Bytes } from '@graphprotocol/graph-ts'; +import { + afterAll, + assert, + beforeEach, + clearStore, + describe, + test, +} from 'matchstick-as/assembly'; + +import { + HMT_STATISTICS_ENTITY_ID, + handleApproval, + handleBulkApproval, + handleBulkTransfer, + handleTransfer, +} from '../../src/mapping/HMToken'; +import { ONE_DAY } from '../../src/mapping/utils/number'; +import { + createApprovalEvent, + createBulkApprovalEvent, + createBulkTransferEvent, + createTransferEvent, +} from './fixtures'; + +const zeroAddressString = '0x0000000000000000000000000000000000000000'; +const operatorAddressString = '0xD979105297fB0eee83F7433fC09279cb5B94fFC6'; +const holderAddressString = '0x70997970c51812dc3a010c7d01b50e0d17dc79c8'; +const holderAddress = Address.fromString(holderAddressString); +const holder1AddressString = '0x92a2eEF7Ff696BCef98957a0189872680600a959'; +const holder1Address = Address.fromString(holder1AddressString); + +describe('HMToken', () => { + beforeEach(() => { + clearStore(); + }); + + afterAll(() => { + clearStore(); + }); + + describe('Statistics', () => { + test('Should properly calculate Transfer event in statistics', () => { + const transfer1 = createTransferEvent( + operatorAddressString, + holder1AddressString, + 1, + BigInt.fromI32(10) + ); + const transfer2 = createTransferEvent( + operatorAddressString, + holder1AddressString, + 2, + BigInt.fromI32(10) + ); + + handleTransfer(transfer1); + handleTransfer(transfer2); + + assert.fieldEquals( + 'HMTokenStatistics', + HMT_STATISTICS_ENTITY_ID.toHex(), + 'totalTransferEventCount', + '2' + ); + assert.fieldEquals( + 'HMTokenStatistics', + HMT_STATISTICS_ENTITY_ID.toHex(), + 'totalValueTransfered', + '3' + ); + }); + + test('Should properly calculate holders in statistics', () => { + const transfer1 = createTransferEvent( + zeroAddressString, + holderAddressString, + 10, + BigInt.fromI32(10) + ); + + const transfer2 = createTransferEvent( + holderAddressString, + holder1AddressString, + 0, + BigInt.fromI32(10) + ); + + handleTransfer(transfer1); + handleTransfer(transfer2); + + assert.fieldEquals('Holder', holderAddress.toHex(), 'balance', '10'); + assert.fieldEquals('Holder', holder1Address.toHex(), 'balance', '0'); + + assert.fieldEquals( + 'HMTokenStatistics', + HMT_STATISTICS_ENTITY_ID.toHex(), + 'holders', + '1' + ); + + const transfer3 = createTransferEvent( + zeroAddressString, + holder1AddressString, + 10, + BigInt.fromI32(10) + ); + + handleTransfer(transfer3); + + assert.fieldEquals('Holder', holderAddress.toHex(), 'balance', '10'); + assert.fieldEquals('Holder', holder1Address.toHex(), 'balance', '10'); + assert.fieldEquals( + 'HMTokenStatistics', + HMT_STATISTICS_ENTITY_ID.toHex(), + 'holders', + '2' + ); + + const transfer4 = createTransferEvent( + holderAddressString, + holder1AddressString, + 10, + BigInt.fromI32(10) + ); + + handleTransfer(transfer4); + + assert.fieldEquals('Holder', holderAddress.toHex(), 'balance', '0'); + assert.fieldEquals('Holder', holder1Address.toHex(), 'balance', '20'); + assert.fieldEquals( + 'HMTokenStatistics', + HMT_STATISTICS_ENTITY_ID.toHex(), + 'holders', + '1' + ); + }); + + test('Should properly calculate Transfer daily metrics in statistics', () => { + const transfer1 = createTransferEvent( + operatorAddressString, + holderAddressString, + 1, + BigInt.fromI32(100) + ); + const transfer2 = createTransferEvent( + operatorAddressString, + holder1AddressString, + 2, + BigInt.fromI32(100) + ); + const transfer3 = createTransferEvent( + holder1AddressString, + holderAddressString, + 3, + BigInt.fromI32(100) + ); + + handleTransfer(transfer1); + handleTransfer(transfer2); + handleTransfer(transfer3); + + const dayZeroId = Bytes.fromI32(0).toHex(); + assert.fieldEquals('EventDayData', dayZeroId, 'timestamp', '0'); + assert.fieldEquals( + 'EventDayData', + dayZeroId, + 'dailyHMTTransferCount', + '3' + ); + assert.fieldEquals( + 'EventDayData', + dayZeroId, + 'dailyHMTTransferAmount', + '6' + ); + assert.fieldEquals('EventDayData', dayZeroId, 'dailyUniqueSenders', '2'); + assert.fieldEquals( + 'EventDayData', + dayZeroId, + 'dailyUniqueReceivers', + '2' + ); + + const operatorSenderId = Bytes.fromI32(0) + .concat(Address.fromString(operatorAddressString)) + .toHex(); + const holder1SenderId = Bytes.fromI32(0).concat(holder1Address).toHex(); + const holderReceiverId = Bytes.fromI32(0).concat(holderAddress).toHex(); + const holder1ReceiverId = Bytes.fromI32(0).concat(holder1Address).toHex(); + + assert.fieldEquals( + 'UniqueSender', + operatorSenderId, + 'transferCount', + '3' + ); + assert.fieldEquals('UniqueSender', holder1SenderId, 'transferCount', '3'); + assert.fieldEquals( + 'UniqueReceiver', + holderReceiverId, + 'receiveCount', + '4' + ); + assert.fieldEquals( + 'UniqueReceiver', + holder1ReceiverId, + 'receiveCount', + '2' + ); + }); + + test('Should properly create separate daily stats per day', () => { + const day0Transfer = createTransferEvent( + zeroAddressString, + holderAddressString, + 5, + BigInt.fromI32(100) + ); + const day1Transfer = createTransferEvent( + zeroAddressString, + holderAddressString, + 7, + BigInt.fromI32(ONE_DAY + 100) + ); + + handleTransfer(day0Transfer); + handleTransfer(day1Transfer); + + assert.entityCount('EventDayData', 2); + + const day0Id = Bytes.fromI32(0).toHex(); + const day1Id = Bytes.fromI32(1).toHex(); + + assert.fieldEquals('EventDayData', day0Id, 'dailyHMTTransferCount', '1'); + assert.fieldEquals('EventDayData', day0Id, 'dailyHMTTransferAmount', '5'); + assert.fieldEquals('EventDayData', day0Id, 'dailyUniqueSenders', '0'); + assert.fieldEquals('EventDayData', day0Id, 'dailyUniqueReceivers', '1'); + + assert.fieldEquals('EventDayData', day1Id, 'dailyHMTTransferCount', '1'); + assert.fieldEquals('EventDayData', day1Id, 'dailyHMTTransferAmount', '7'); + assert.fieldEquals('EventDayData', day1Id, 'dailyUniqueSenders', '0'); + assert.fieldEquals('EventDayData', day1Id, 'dailyUniqueReceivers', '1'); + }); + + test('Should properly calculate BulkTransfer event in statistics', () => { + const bulkTransfer1 = createBulkTransferEvent(1, 3, BigInt.fromI32(10)); + const bulkTransfer2 = createBulkTransferEvent(2, 3, BigInt.fromI32(10)); + + handleBulkTransfer(bulkTransfer1); + handleBulkTransfer(bulkTransfer2); + + assert.fieldEquals( + 'HMTokenStatistics', + HMT_STATISTICS_ENTITY_ID.toHex(), + 'totalBulkTransferEventCount', + '2' + ); + }); + + test('Should properly calculate Approval event in statistics', () => { + const approval1 = createApprovalEvent( + holder1AddressString, + operatorAddressString, + 10, + BigInt.fromI32(10) + ); + + const approval2 = createApprovalEvent( + holder1AddressString, + operatorAddressString, + 10, + BigInt.fromI32(10) + ); + + handleApproval(approval1); + handleApproval(approval2); + + assert.fieldEquals( + 'HMTokenStatistics', + HMT_STATISTICS_ENTITY_ID.toHex(), + 'totalApprovalEventCount', + '2' + ); + }); + + test('Should properly calculate BulkApproval event in statistics', () => { + const bulkApproval1 = createBulkApprovalEvent(1, 3, BigInt.fromI32(10)); + const bulkApproval2 = createBulkApprovalEvent(2, 3, BigInt.fromI32(10)); + + handleBulkApproval(bulkApproval1); + handleBulkApproval(bulkApproval2); + + assert.fieldEquals( + 'HMTokenStatistics', + HMT_STATISTICS_ENTITY_ID.toHex(), + 'totalBulkApprovalEventCount', + '2' + ); + }); + }); +}); diff --git a/packages/subgraph/hmt/tests/utils.ts b/packages/subgraph/hmt/tests/utils.ts new file mode 100644 index 0000000000..95dd417cce --- /dev/null +++ b/packages/subgraph/hmt/tests/utils.ts @@ -0,0 +1,11 @@ +import { BigInt, Bytes } from '@graphprotocol/graph-ts'; + +export function generateUniqueHash( + value: string, + timestamp: BigInt, + nonce: BigInt +): Bytes { + const uniqueString = + value + '-' + timestamp.toString() + '-' + nonce.toString(); + return Bytes.fromUTF8(uniqueString); +} diff --git a/packages/sdk/typescript/subgraph/tsconfig.json b/packages/subgraph/hmt/tsconfig.json similarity index 73% rename from packages/sdk/typescript/subgraph/tsconfig.json rename to packages/subgraph/hmt/tsconfig.json index f9e6cb7174..3c11a89fec 100644 --- a/packages/sdk/typescript/subgraph/tsconfig.json +++ b/packages/subgraph/hmt/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../../tsconfig.base.json", + "extends": "../../../tsconfig.base.json", "compilerOptions": { "baseUrl": ".", "types": ["@graphprotocol/graph-ts", "node"] diff --git a/packages/sdk/typescript/subgraph/.gitignore b/packages/subgraph/human-protocol/.gitignore similarity index 100% rename from packages/sdk/typescript/subgraph/.gitignore rename to packages/subgraph/human-protocol/.gitignore diff --git a/packages/subgraph/human-protocol/.prettierignore b/packages/subgraph/human-protocol/.prettierignore new file mode 100644 index 0000000000..55be744ac5 --- /dev/null +++ b/packages/subgraph/human-protocol/.prettierignore @@ -0,0 +1,2 @@ +# Mustache template; not valid YAML for Prettier's YAML parser +template.yaml diff --git a/packages/subgraph/human-protocol/.prettierrc b/packages/subgraph/human-protocol/.prettierrc new file mode 100644 index 0000000000..4a2b9855ac --- /dev/null +++ b/packages/subgraph/human-protocol/.prettierrc @@ -0,0 +1,7 @@ +{ + "semi": true, + "trailingComma": "es5", + "singleQuote": true, + "printWidth": 80, + "arrowParens": "always" +} diff --git a/packages/sdk/typescript/subgraph/README.md b/packages/subgraph/human-protocol/README.md similarity index 100% rename from packages/sdk/typescript/subgraph/README.md rename to packages/subgraph/human-protocol/README.md diff --git a/packages/subgraph/human-protocol/config/amoy.json b/packages/subgraph/human-protocol/config/amoy.json new file mode 100644 index 0000000000..81bb09b1f4 --- /dev/null +++ b/packages/subgraph/human-protocol/config/amoy.json @@ -0,0 +1,25 @@ +{ + "network": "polygon-amoy", + "description": "HUMAN subgraph on Amoy Testnet", + "EscrowFactory": { + "address": "0xAFf5a986A530ff839d49325A5dF69F96627E8D29", + "startBlock": 5773000, + "abi": "../../../node_modules/@human-protocol/core/abis/EscrowFactory.json" + }, + "HMToken": { + "address": "0x792abbcC99c01dbDec49c9fa9A828a186Da45C33" + }, + "Escrow": { + "abi": "../../../node_modules/@human-protocol/core/abis/Escrow.json" + }, + "KVStore": { + "address": "0x724AeFC243EdacCA27EAB86D3ec5a76Af4436Fc7", + "startBlock": 5773002, + "abi": "../../../node_modules/@human-protocol/core/abis/KVStore.json" + }, + "Staking": { + "address": "0xffE496683F842a923110415b7278ded3F265f2C5", + "startBlock": 14983952, + "abi": "../../../node_modules/@human-protocol/core/abis/Staking.json" + } +} diff --git a/packages/subgraph/human-protocol/config/bsc-testnet.json b/packages/subgraph/human-protocol/config/bsc-testnet.json new file mode 100644 index 0000000000..636e82c61d --- /dev/null +++ b/packages/subgraph/human-protocol/config/bsc-testnet.json @@ -0,0 +1,33 @@ +{ + "network": "chapel", + "description": "Human subgraph on Binance Smart Chain testnet", + "EscrowFactory": { + "address": "0x2bfA592DBDaF434DDcbb893B1916120d181DAD18", + "startBlock": 26716359, + "abi": "../../../node_modules/@human-protocol/core/abis/EscrowFactory.json" + }, + "HMToken": { + "address": "0xE3D74BBFa45B4bCa69FF28891fBE392f4B4d4e4d" + }, + "Escrow": { + "abi": "../../../node_modules/@human-protocol/core/abis/Escrow.json" + }, + "Staking": { + "address": "0xD6D347ba6987519B4e42EcED43dF98eFf5465a23", + "startBlock": 45938762, + "abi": "../../../node_modules/@human-protocol/core/abis/Staking.json" + }, + "KVStore": { + "address": "0x32e27177BA6Ea91cf28dfd91a0Da9822A4b74EcF", + "startBlock": 34883905, + "abi": "../../../node_modules/@human-protocol/core/abis/KVStore.json" + }, + "LegacyEscrowFactory": { + "address": "0xaae6a2646c1f88763e62e0cd08ad050ea66ac46f", + "startBlock": 23632686, + "abi": "../../../node_modules/@human-protocol/core/abis/legacy/EscrowFactory.json" + }, + "LegacyEscrow": { + "abi": "../../../node_modules/@human-protocol/core/abis/legacy/Escrow.json" + } +} diff --git a/packages/subgraph/human-protocol/config/bsc.json b/packages/subgraph/human-protocol/config/bsc.json new file mode 100644 index 0000000000..e7144d3720 --- /dev/null +++ b/packages/subgraph/human-protocol/config/bsc.json @@ -0,0 +1,33 @@ +{ + "network": "bsc", + "description": "Human subgraph on BSC mainnet", + "EscrowFactory": { + "address": "0x92FD968AcBd521c232f5fB8c33b342923cC72714", + "startBlock": 28745625, + "abi": "../../../node_modules/@human-protocol/core/abis/EscrowFactory.json" + }, + "HMToken": { + "address": "0x711Fd6ab6d65A98904522d4e3586F492B989c527" + }, + "Escrow": { + "abi": "../../../node_modules/@human-protocol/core/abis/Escrow.json" + }, + "Staking": { + "address": "0xE24e5C08E28331D24758b69A5E9f383D2bDD1c98", + "startBlock": 45120420, + "abi": "../../../node_modules/@human-protocol/core/abis/Staking.json" + }, + "KVStore": { + "address": "0x21A0C4CED7aE447fCf87D9FE3A29FA9B3AB20Ff1", + "startBlock": 33941535, + "abi": "../../../node_modules/@human-protocol/core/abis/KVStore.json" + }, + "LegacyEscrowFactory": { + "address": "0xc88bC422cAAb2ac8812de03176402dbcA09533f4", + "startBlock": 20689161, + "abi": "../../../node_modules/@human-protocol/core/abis/legacy/EscrowFactory.json" + }, + "LegacyEscrow": { + "abi": "../../../node_modules/@human-protocol/core/abis/legacy/Escrow.json" + } +} diff --git a/packages/subgraph/human-protocol/config/ethereum.json b/packages/subgraph/human-protocol/config/ethereum.json new file mode 100644 index 0000000000..5c03c57f35 --- /dev/null +++ b/packages/subgraph/human-protocol/config/ethereum.json @@ -0,0 +1,33 @@ +{ + "network": "mainnet", + "description": "Human subgraph on Ethereum Mainnet", + "EscrowFactory": { + "address": "0xD9c75a1Aa4237BB72a41E5E26bd8384f10c1f55a", + "startBlock": 16924057, + "abi": "../../../node_modules/@human-protocol/core/abis/EscrowFactory.json" + }, + "HMToken": { + "address": "0xd1ba9BAC957322D6e8c07a160a3A8dA11A0d2867" + }, + "Escrow": { + "abi": "../../../node_modules/@human-protocol/core/abis/Escrow.json" + }, + "KVStore": { + "address": "0xB6d36B1CDaD50302BCB3DB43bAb0D349458e1b8D", + "startBlock": 18683644, + "abi": "../../../node_modules/@human-protocol/core/abis/KVStore.json" + }, + "Staking": { + "address": "0xEf6Da3aB52c33925Be3F84038193a7e1331F51E6", + "startBlock": 21464165, + "abi": "../../../node_modules/@human-protocol/core/abis/Staking.json" + }, + "LegacyEscrowFactory": { + "address": "0xD9c75a1Aa4237BB72a41E5E26bd8384f10c1f55a", + "startBlock": 16924057, + "abi": "../../../node_modules/@human-protocol/core/abis/legacy/EscrowFactory.json" + }, + "LegacyEscrow": { + "abi": "../../../node_modules/@human-protocol/core/abis/legacy/Escrow.json" + } +} diff --git a/packages/subgraph/human-protocol/config/localhost.json b/packages/subgraph/human-protocol/config/localhost.json new file mode 100644 index 0000000000..9998836d75 --- /dev/null +++ b/packages/subgraph/human-protocol/config/localhost.json @@ -0,0 +1,25 @@ +{ + "network": "localhost", + "description": "Human subgraph on localhost", + "EscrowFactory": { + "address": "0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9", + "startBlock": 5, + "abi": "../../../node_modules/@human-protocol/core/abis/EscrowFactory.json" + }, + "HMToken": { + "address": "0x5fbdb2315678afecb367f032d93f642f64180aa3" + }, + "Escrow": { + "abi": "../../../node_modules/@human-protocol/core/abis/Escrow.json" + }, + "Staking": { + "address": "0xe7f1725e7734ce288f8367e1bb143e90bb3f0512", + "startBlock": 3, + "abi": "../../../node_modules/@human-protocol/core/abis/Staking.json" + }, + "KVStore": { + "address": "0xdc64a140aa3e981100a9beca4e685f962f0cf6c9", + "startBlock": 6, + "abi": "../../../node_modules/@human-protocol/core/abis/KVStore.json" + } +} diff --git a/packages/subgraph/human-protocol/config/polygon.json b/packages/subgraph/human-protocol/config/polygon.json new file mode 100644 index 0000000000..c1a584b909 --- /dev/null +++ b/packages/subgraph/human-protocol/config/polygon.json @@ -0,0 +1,33 @@ +{ + "network": "matic", + "description": "Human subgraph on Polygon mainnet", + "EscrowFactory": { + "address": "0xBDBfD2cC708199C5640C6ECdf3B0F4A4C67AdfcB", + "startBlock": 38858552, + "abi": "../../../node_modules/@human-protocol/core/abis/EscrowFactory.json" + }, + "HMToken": { + "address": "0xc748B2A084F8eFc47E086ccdDD9b7e67aEb571Bf" + }, + "Escrow": { + "abi": "../../../node_modules/@human-protocol/core/abis/Escrow.json" + }, + "Staking": { + "address": "0x01D115E9E8bF0C58318793624CC662a030D07F1D", + "startBlock": 65832028, + "abi": "../../../node_modules/@human-protocol/core/abis/Staking.json" + }, + "KVStore": { + "address": "0xbcB28672F826a50B03EE91B28145EAbddA73B2eD", + "startBlock": 50567977, + "abi": "../../../node_modules/@human-protocol/core/abis/KVStore.json" + }, + "LegacyEscrowFactory": { + "address": "0x45eBc3eAE6DA485097054ae10BA1A0f8e8c7f794", + "startBlock": 25426566, + "abi": "../../../node_modules/@human-protocol/core/abis/legacy/EscrowFactory.json" + }, + "LegacyEscrow": { + "abi": "../../../node_modules/@human-protocol/core/abis/legacy/Escrow.json" + } +} diff --git a/packages/subgraph/human-protocol/config/sepolia.json b/packages/subgraph/human-protocol/config/sepolia.json new file mode 100644 index 0000000000..eca908cad1 --- /dev/null +++ b/packages/subgraph/human-protocol/config/sepolia.json @@ -0,0 +1,28 @@ +{ + "network": "sepolia", + "description": "HUMAN subgraph on Sepolia Ethereum Testnet", + "EscrowFactory": { + "address": "0x5987A5558d961ee674efe4A8c8eB7B1b5495D3bf", + "startBlock": 7067993, + "abi": "../../../node_modules/@human-protocol/core/abis/EscrowFactory.json" + }, + "HMToken": { + "address": "0x792abbcC99c01dbDec49c9fa9A828a186Da45C33" + }, + "Escrow": { + "abi": "../../../node_modules/@human-protocol/core/abis/Escrow.json" + }, + "KVStore": { + "address": "0xCc0AF0635aa19fE799B6aFDBe28fcFAeA7f00a60", + "startBlock": 5716238, + "abi": "../../../node_modules/@human-protocol/core/abis/KVStore.json" + }, + "Staking": { + "address": "0x2163e3A40032Af1C359ac731deaB48258b317890", + "startBlock": 7062708, + "abi": "../../../node_modules/@human-protocol/core/abis/Staking.json" + }, + "LegacyEscrow": { + "abi": "../../../node_modules/@human-protocol/core/abis/legacy/Escrow.json" + } +} diff --git a/packages/subgraph/human-protocol/eslint.config.mjs b/packages/subgraph/human-protocol/eslint.config.mjs new file mode 100644 index 0000000000..666059c3d0 --- /dev/null +++ b/packages/subgraph/human-protocol/eslint.config.mjs @@ -0,0 +1,59 @@ +import eslint from '@eslint/js'; +import globals from 'globals'; +import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; +import tseslint from 'typescript-eslint'; +import { flatConfigs as graphqlFlatConfigs } from '@graphql-eslint/eslint-plugin'; +const graphqlOperations = graphqlFlatConfigs['operations-recommended']; + +export default tseslint.config( + { + ignores: ['build', 'generated', 'schema.graphql'], + }, + eslint.configs.recommended, + tseslint.configs.recommended, + eslintPluginPrettierRecommended, + { + files: ['**/*.ts', '**/*.js'], + languageOptions: { + globals: { + ...globals.node, + ...globals.es2022, + }, + ecmaVersion: 2022, + sourceType: 'module', + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, + }, + rules: { + 'no-useless-assignment': 'off', + 'preserve-caught-error': 'off', + 'no-console': 'warn', + '@/quotes': [ + 'error', + 'single', + { avoidEscape: true, allowTemplateLiterals: true }, + ], + }, + }, + { + files: ['**/*.graphql'], + languageOptions: { + ...(graphqlOperations.languageOptions ?? {}), + parserOptions: { + ...(graphqlOperations.languageOptions?.parserOptions ?? {}), + schema: './schema.graphql', + }, + }, + rules: graphqlOperations.rules, + }, + { + files: ['**/*.spec.ts', '**/*.spec.tsx', '**/*.test.ts', '**/*.test.tsx'], + languageOptions: { + globals: { + ...globals.jest, + }, + }, + } +); diff --git a/packages/sdk/typescript/subgraph/local-graph-status.sh b/packages/subgraph/human-protocol/local-graph-status.sh similarity index 100% rename from packages/sdk/typescript/subgraph/local-graph-status.sh rename to packages/subgraph/human-protocol/local-graph-status.sh diff --git a/packages/subgraph/human-protocol/matchstick.yaml b/packages/subgraph/human-protocol/matchstick.yaml new file mode 100644 index 0000000000..be49651fb8 --- /dev/null +++ b/packages/subgraph/human-protocol/matchstick.yaml @@ -0,0 +1 @@ +libsFolder: ../../../node_modules diff --git a/packages/sdk/typescript/subgraph/package.json b/packages/subgraph/human-protocol/package.json similarity index 88% rename from packages/sdk/typescript/subgraph/package.json rename to packages/subgraph/human-protocol/package.json index 3a84c97f38..b6c59674ca 100644 --- a/packages/sdk/typescript/subgraph/package.json +++ b/packages/subgraph/human-protocol/package.json @@ -8,9 +8,8 @@ ], "scripts": { "clean": "rm -rf build generated subgraph.yaml", - "generate": "mustache ./config/$NETWORK.json template.yaml > subgraph.yaml && yarn generate-escrow && yarn generate-hmt && yarn generate-staking && graph codegen", + "generate": "mustache ./config/$NETWORK.json template.yaml > subgraph.yaml && yarn generate-escrow && yarn generate-staking && graph codegen", "generate-escrow": "mustache ./config/$NETWORK.json src/mapping/EscrowTemplate.ts > src/mapping/Escrow.ts", - "generate-hmt": "mustache ./config/$NETWORK.json src/mapping/HMTokenTemplate.ts > src/mapping/HMToken.ts", "generate-staking": "mustache ./config/$NETWORK.json src/mapping/StakingTemplate.ts > src/mapping/Staking.ts", "codegen": "graph codegen", "build": "graph build", @@ -27,7 +26,7 @@ "repository": { "type": "git", "url": "https://github.com/humanprotocol/human-protocol.git", - "directory": "packages/sdk/typescript/subgraph" + "directory": "packages/sdk/typescript/subgraph/human-protocol" }, "keywords": [ "human-protocol", diff --git a/packages/sdk/typescript/subgraph/schema.graphql b/packages/subgraph/human-protocol/schema.graphql similarity index 81% rename from packages/sdk/typescript/subgraph/schema.graphql rename to packages/subgraph/human-protocol/schema.graphql index e4d122605c..82bbd4f403 100644 --- a/packages/sdk/typescript/subgraph/schema.graphql +++ b/packages/subgraph/human-protocol/schema.graphql @@ -2,33 +2,12 @@ # Entities # ################################################## -type Holder @entity(immutable: false) { - id: Bytes! - address: Bytes! # address - balance: BigInt! -} - type Worker @entity(immutable: false) { id: Bytes! address: Bytes! - totalHMTAmountReceived: BigInt! payoutCount: BigInt! } -type UniqueSender @entity(immutable: false) { - id: Bytes! - address: Bytes! - transferCount: BigInt! - timestamp: BigInt! -} - -type UniqueReceiver @entity(immutable: false) { - id: Bytes! - address: Bytes! - receiveCount: BigInt! - timestamp: BigInt! -} - type Operator @entity(immutable: false) { id: Bytes! address: Bytes! @@ -111,49 +90,6 @@ type DailyWorker @entity(immutable: true) { # Events # ################################################## -# HMToken -type HMTTransferEvent @entity(immutable: true) { - id: Bytes! - block: BigInt! - timestamp: BigInt! - txHash: Bytes! - - from: Bytes! # address - to: Bytes! # address - amount: BigInt! -} - -type HMTBulkTransferEvent @entity(immutable: true) { - id: Bytes! - block: BigInt! - timestamp: BigInt! - txHash: Bytes! - - txId: BigInt! - bulkCount: BigInt! -} - -type HMTApprovalEvent @entity(immutable: true) { - id: Bytes! - block: BigInt! - timestamp: BigInt! - txHash: Bytes! - - owner: Bytes! # address - spender: Bytes! # address - amount: BigInt! -} - -type HMTBulkApprovalEvent @entity(immutable: true) { - id: Bytes! - block: BigInt! - timestamp: BigInt! - txHash: Bytes! - - txId: BigInt! - bulkCount: BigInt! -} - # Escrow type FundEvent @entity(immutable: true) { id: Bytes! @@ -316,16 +252,6 @@ type StakeSlashedEvent @entity(immutable: true) { # Statistics # ################################################## -type HMTokenStatistics @entity(immutable: false) { - id: Bytes! - totalTransferEventCount: BigInt! - totalBulkTransferEventCount: BigInt! - totalApprovalEventCount: BigInt! - totalBulkApprovalEventCount: BigInt! - totalValueTransfered: BigInt! - holders: BigInt! -} - type EscrowStatistics @entity(immutable: false) { id: Bytes! fundEventCount: BigInt! @@ -357,11 +283,6 @@ type EventDayData @entity(immutable: false) { dailyEscrowCount: BigInt! dailyWorkerCount: BigInt! dailyPayoutCount: BigInt! - dailyHMTPayoutAmount: BigInt! - dailyHMTTransferCount: BigInt! - dailyHMTTransferAmount: BigInt! - dailyUniqueSenders: BigInt! - dailyUniqueReceivers: BigInt! } ################################################## diff --git a/packages/sdk/typescript/subgraph/src/mapping/EscrowFactory.ts b/packages/subgraph/human-protocol/src/mapping/EscrowFactory.ts similarity index 100% rename from packages/sdk/typescript/subgraph/src/mapping/EscrowFactory.ts rename to packages/subgraph/human-protocol/src/mapping/EscrowFactory.ts diff --git a/packages/sdk/typescript/subgraph/src/mapping/EscrowTemplate.ts b/packages/subgraph/human-protocol/src/mapping/EscrowTemplate.ts similarity index 87% rename from packages/sdk/typescript/subgraph/src/mapping/EscrowTemplate.ts rename to packages/subgraph/human-protocol/src/mapping/EscrowTemplate.ts index 83d89d7f0a..e2da0edfdb 100644 --- a/packages/sdk/typescript/subgraph/src/mapping/EscrowTemplate.ts +++ b/packages/subgraph/human-protocol/src/mapping/EscrowTemplate.ts @@ -9,6 +9,7 @@ import { Pending, Fund, PendingV2, + PendingV3, Withdraw, CancellationRequested, CancellationRefund, @@ -43,7 +44,6 @@ import { createTransaction } from './utils/transaction'; import { toBytes } from './utils/string'; import { createOrLoadOperator } from './KVStore'; -export const HMT_ADDRESS = Address.fromString('{{ HMToken.address }}'); export const STATISTICS_ENTITY_ID = toBytes('escrow-statistics-id'); function constructStatsEntity(): EscrowStatistics { @@ -80,7 +80,6 @@ export function createOrLoadWorker(address: Address): Worker { if (!worker) { worker = new Worker(address); worker.address = address; - worker.totalHMTAmountReceived = ZERO_BI; worker.payoutCount = ZERO_BI; } @@ -144,7 +143,10 @@ function updateEscrowEntityForPending( manifestHash: string, reputationOracle: Address | null = null, recordingOracle: Address | null = null, - exchangeOracle: Address | null = null + exchangeOracle: Address | null = null, + reputationOracleFee: BigInt | null = null, + recordingOracleFee: BigInt | null = null, + exchangeOracleFee: BigInt | null = null ): void { escrowEntity.manifest = manifest; escrowEntity.manifestHash = manifestHash; @@ -153,25 +155,37 @@ function updateEscrowEntityForPending( // Update oracles if provided if (reputationOracle) { escrowEntity.reputationOracle = reputationOracle; - const reputationOracleEntity = Operator.load(reputationOracle); - if (reputationOracleEntity) { - escrowEntity.reputationOracleFee = reputationOracleEntity.fee; + if (reputationOracleFee) { + escrowEntity.reputationOracleFee = reputationOracleFee; + } else { + const reputationOracleEntity = Operator.load(reputationOracle); + if (reputationOracleEntity) { + escrowEntity.reputationOracleFee = reputationOracleEntity.fee; + } } } if (recordingOracle) { escrowEntity.recordingOracle = recordingOracle; - const recordingOracleEntity = Operator.load(recordingOracle); - if (recordingOracleEntity) { - escrowEntity.recordingOracleFee = recordingOracleEntity.fee; + if (recordingOracleFee) { + escrowEntity.recordingOracleFee = recordingOracleFee; + } else { + const recordingOracleEntity = Operator.load(recordingOracle); + if (recordingOracleEntity) { + escrowEntity.recordingOracleFee = recordingOracleEntity.fee; + } } } if (exchangeOracle) { escrowEntity.exchangeOracle = exchangeOracle; - const exchangeOracleEntity = Operator.load(exchangeOracle); - if (exchangeOracleEntity) { - escrowEntity.exchangeOracleFee = exchangeOracleEntity.fee; + if (exchangeOracleFee) { + escrowEntity.exchangeOracleFee = exchangeOracleFee; + } else { + const exchangeOracleEntity = Operator.load(exchangeOracle); + if (exchangeOracleEntity) { + escrowEntity.exchangeOracleFee = exchangeOracleEntity.fee; + } } } @@ -283,6 +297,43 @@ export function handlePendingV2(event: PendingV2): void { } } +export function handlePendingV3(event: PendingV3): void { + // Create common entities for setup and status + const escrowStatusEvent = createCommonEntitiesForPending(event, 'Pending'); + + // Update statistics + updateStatisticsForPending(); + + // Update event day data + updateEventDayDataForPending(event); + + // Update escrow entity + const escrowEntity = Escrow.load(dataSource.address()); + if (escrowEntity) { + updateEscrowEntityForPending( + escrowEntity, + escrowStatusEvent, + event.params.manifest, + event.params.hash, + event.params.reputationOracle, + event.params.recordingOracle, + event.params.exchangeOracle, + BigInt.fromI32(event.params.reputationOracleFeePercentage), + BigInt.fromI32(event.params.recordingOracleFeePercentage), + BigInt.fromI32(event.params.exchangeOracleFeePercentage) + ); + + createTransaction( + event, + 'setup', + event.transaction.from, + Address.fromBytes(escrowEntity.address), + null, + Address.fromBytes(escrowEntity.address) + ); + } +} + export function handleIntermediateStorage(event: IntermediateStorage): void { // Create StoreResultsEvent entity const eventEntity = new StoreResultsEvent(toEventId(event)); @@ -547,21 +598,20 @@ function handleBulkTransferCommon( } eventDayData.save(); - // If the escrow is non-HMT, create the internal transaction - if (Address.fromBytes(escrowEntity.token) != HMT_ADDRESS) { - const internalTransaction = new InternalTransaction( - event.transaction.hash - .concatI32(i) - .concatI32(event.block.timestamp.toI32()) - ); - internalTransaction.from = Address.fromBytes(escrowEntity.address); - internalTransaction.to = recipient; - internalTransaction.value = amount; - internalTransaction.transaction = transaction.id; - internalTransaction.method = 'transfer'; - internalTransaction.escrow = Address.fromBytes(escrowEntity.address); - internalTransaction.save(); - } + const internalTransaction = new InternalTransaction( + event.transaction.hash + .concatI32(i) + .concatI32(event.block.timestamp.toI32()) + ); + internalTransaction.from = Address.fromBytes(escrowEntity.address); + internalTransaction.to = recipient; + internalTransaction.value = amount; + internalTransaction.transaction = transaction.id; + internalTransaction.method = 'transfer'; + internalTransaction.escrow = Address.fromBytes(escrowEntity.address); + internalTransaction.token = Address.fromBytes(escrowEntity.token); + internalTransaction.receiver = recipient; + internalTransaction.save(); } // Assign finalResultsUrl directly from the event @@ -685,11 +735,7 @@ export function handleCompleted(event: Completed): void { null, Address.fromBytes(escrowEntity.address) ); - if ( - escrowEntity.balance && - escrowEntity.balance.gt(ZERO_BI) && - escrowEntity.token != HMT_ADDRESS - ) { + if (escrowEntity.balance && escrowEntity.balance.gt(ZERO_BI)) { const internalTransaction = new InternalTransaction(toEventId(event)); internalTransaction.from = escrowEntity.address; internalTransaction.to = escrowEntity.launcher; @@ -698,6 +744,7 @@ export function handleCompleted(event: Completed): void { internalTransaction.method = 'transfer'; internalTransaction.escrow = escrowEntity.address; internalTransaction.token = escrowEntity.token; + internalTransaction.receiver = escrowEntity.launcher; internalTransaction.save(); escrowEntity.balance = ZERO_BI; @@ -716,6 +763,17 @@ export function handleFund(event: Fund): void { return; } + createTransaction( + event, + 'fund', + event.transaction.from, + Address.fromBytes(escrowEntity.address), + null, + Address.fromBytes(escrowEntity.address), + event.params.amount, + Address.fromBytes(escrowEntity.token) + ); + // Create FundEvent entity const fundEventEntity = new FundEvent(toEventId(event)); fundEventEntity.block = event.block.number; @@ -743,9 +801,7 @@ export function handleFund(event: Fund): void { // Update escrow entity escrowEntity.totalFundedAmount = event.params.amount; - if (escrowEntity.token != HMT_ADDRESS) { - escrowEntity.balance = event.params.amount; - } + escrowEntity.balance = event.params.amount; escrowEntity.save(); } @@ -841,18 +897,15 @@ export function handleCancellationRefund(event: CancellationRefund): void { event.params.amount, Address.fromBytes(escrowEntity.token) ); - if (Address.fromBytes(escrowEntity.token) != HMT_ADDRESS) { - // If escrow is funded with HMT, balance is already tracked by HMT transfer - const internalTransaction = new InternalTransaction(toEventId(event)); - internalTransaction.from = escrowEntity.address; - internalTransaction.to = Address.fromBytes(escrowEntity.token); - internalTransaction.receiver = escrowEntity.canceler; - internalTransaction.value = escrowEntity.balance; - internalTransaction.transaction = transaction.id; - internalTransaction.method = 'transfer'; - internalTransaction.token = Address.fromBytes(escrowEntity.token); - internalTransaction.save(); - } + const internalTransaction = new InternalTransaction(toEventId(event)); + internalTransaction.from = escrowEntity.address; + internalTransaction.to = Address.fromBytes(escrowEntity.token); + internalTransaction.receiver = escrowEntity.canceler; + internalTransaction.value = escrowEntity.balance; + internalTransaction.transaction = transaction.id; + internalTransaction.method = 'transfer'; + internalTransaction.token = Address.fromBytes(escrowEntity.token); + internalTransaction.save(); escrowEntity.balance = escrowEntity.balance.minus(event.params.amount); escrowEntity.save(); diff --git a/packages/sdk/typescript/subgraph/src/mapping/KVStore.ts b/packages/subgraph/human-protocol/src/mapping/KVStore.ts similarity index 100% rename from packages/sdk/typescript/subgraph/src/mapping/KVStore.ts rename to packages/subgraph/human-protocol/src/mapping/KVStore.ts diff --git a/packages/sdk/typescript/subgraph/src/mapping/StakingTemplate.ts b/packages/subgraph/human-protocol/src/mapping/StakingTemplate.ts similarity index 100% rename from packages/sdk/typescript/subgraph/src/mapping/StakingTemplate.ts rename to packages/subgraph/human-protocol/src/mapping/StakingTemplate.ts diff --git a/packages/sdk/typescript/subgraph/src/mapping/legacy/Escrow.ts b/packages/subgraph/human-protocol/src/mapping/legacy/Escrow.ts similarity index 100% rename from packages/sdk/typescript/subgraph/src/mapping/legacy/Escrow.ts rename to packages/subgraph/human-protocol/src/mapping/legacy/Escrow.ts diff --git a/packages/sdk/typescript/subgraph/src/mapping/legacy/EscrowFactory.ts b/packages/subgraph/human-protocol/src/mapping/legacy/EscrowFactory.ts similarity index 100% rename from packages/sdk/typescript/subgraph/src/mapping/legacy/EscrowFactory.ts rename to packages/subgraph/human-protocol/src/mapping/legacy/EscrowFactory.ts diff --git a/packages/sdk/typescript/subgraph/src/mapping/utils/dayUpdates.ts b/packages/subgraph/human-protocol/src/mapping/utils/dayUpdates.ts similarity index 84% rename from packages/sdk/typescript/subgraph/src/mapping/utils/dayUpdates.ts rename to packages/subgraph/human-protocol/src/mapping/utils/dayUpdates.ts index 6d615b15a5..463f0db316 100644 --- a/packages/sdk/typescript/subgraph/src/mapping/utils/dayUpdates.ts +++ b/packages/subgraph/human-protocol/src/mapping/utils/dayUpdates.ts @@ -26,11 +26,6 @@ export function getEventDayData(event: ethereum.Event): EventDayData { eventDayData.dailyEscrowCount = ZERO_BI; eventDayData.dailyWorkerCount = ZERO_BI; eventDayData.dailyPayoutCount = ZERO_BI; - eventDayData.dailyHMTPayoutAmount = ZERO_BI; - eventDayData.dailyHMTTransferCount = ZERO_BI; - eventDayData.dailyHMTTransferAmount = ZERO_BI; - eventDayData.dailyUniqueSenders = ZERO_BI; - eventDayData.dailyUniqueReceivers = ZERO_BI; } return eventDayData; } diff --git a/packages/sdk/typescript/subgraph/src/mapping/utils/ethAdrress.ts b/packages/subgraph/human-protocol/src/mapping/utils/ethAdrress.ts similarity index 100% rename from packages/sdk/typescript/subgraph/src/mapping/utils/ethAdrress.ts rename to packages/subgraph/human-protocol/src/mapping/utils/ethAdrress.ts diff --git a/packages/sdk/typescript/subgraph/src/mapping/utils/event.ts b/packages/subgraph/human-protocol/src/mapping/utils/event.ts similarity index 100% rename from packages/sdk/typescript/subgraph/src/mapping/utils/event.ts rename to packages/subgraph/human-protocol/src/mapping/utils/event.ts diff --git a/packages/subgraph/human-protocol/src/mapping/utils/number.ts b/packages/subgraph/human-protocol/src/mapping/utils/number.ts new file mode 100644 index 0000000000..476fba0237 --- /dev/null +++ b/packages/subgraph/human-protocol/src/mapping/utils/number.ts @@ -0,0 +1,6 @@ +import { BigInt } from '@graphprotocol/graph-ts'; + +export const ZERO_BI = BigInt.fromI32(0); +export const ONE_BI = BigInt.fromI32(1); + +export const ONE_DAY = 86400; diff --git a/packages/sdk/typescript/subgraph/src/mapping/utils/string.ts b/packages/subgraph/human-protocol/src/mapping/utils/string.ts similarity index 100% rename from packages/sdk/typescript/subgraph/src/mapping/utils/string.ts rename to packages/subgraph/human-protocol/src/mapping/utils/string.ts diff --git a/packages/sdk/typescript/subgraph/src/mapping/utils/transaction.ts b/packages/subgraph/human-protocol/src/mapping/utils/transaction.ts similarity index 100% rename from packages/sdk/typescript/subgraph/src/mapping/utils/transaction.ts rename to packages/subgraph/human-protocol/src/mapping/utils/transaction.ts diff --git a/packages/sdk/typescript/subgraph/template.yaml b/packages/subgraph/human-protocol/template.yaml similarity index 83% rename from packages/sdk/typescript/subgraph/template.yaml rename to packages/subgraph/human-protocol/template.yaml index fa3b45fd4f..1f7d461152 100644 --- a/packages/sdk/typescript/subgraph/template.yaml +++ b/packages/subgraph/human-protocol/template.yaml @@ -33,46 +33,6 @@ dataSources: handler: handleLaunched - event: LaunchedV2(address,address,string) handler: handleLaunchedV2 - - kind: ethereum - name: HMToken - network: '{{ network }}' - source: - abi: HMToken - address: '{{ HMToken.address }}' - startBlock: {{ HMToken.startBlock }} - mapping: - kind: ethereum/events - apiVersion: 0.0.7 - language: wasm/assemblyscript - entities: - - DailyWorker - - Escrow - - EventDayData - - HMTApprovalEvent - - HMTBulkApprovalEvent - - HMTBulkTransferEvent - - HMTTransferEvent - - HMTokenStatistics - - Holder - - Payout - - UniqueReceiver - - UniqueSender - - Worker - - Transaction - - InternalTransaction - abis: - - name: HMToken - file: '{{{ HMToken.abi }}}' - eventHandlers: - - event: Approval(indexed address,indexed address,uint256) - handler: handleApproval - - event: BulkApproval(indexed uint256,uint256) - handler: handleBulkApproval - - event: BulkTransfer(indexed uint256,uint256) - handler: handleBulkTransfer - - event: Transfer(indexed address,indexed address,uint256) - handler: handleTransfer - file: ./src/mapping/HMToken.ts - kind: ethereum name: KVStore network: '{{ network }}' @@ -196,6 +156,8 @@ templates: handler: handlePending - event: PendingV2(string,string,address,address,address) handler: handlePendingV2 + - event: PendingV3(string,string,address,address,address,uint8,uint8,uint8) + handler: handlePendingV3 - event: BulkTransfer(indexed uint256,address[],uint256[],bool) handler: handleBulkTransfer - event: BulkTransferV2(indexed uint256,address[],uint256[],bool,string) @@ -244,4 +206,4 @@ templates: handler: handlePending - event: BulkTransfer(indexed uint256,uint256) handler: handleBulkTransfer -{{ /LegacyEscrow }} \ No newline at end of file +{{ /LegacyEscrow }} diff --git a/packages/sdk/typescript/subgraph/tests/escrow-factory/escrow-factory.test.ts b/packages/subgraph/human-protocol/tests/escrow-factory/escrow-factory.test.ts similarity index 100% rename from packages/sdk/typescript/subgraph/tests/escrow-factory/escrow-factory.test.ts rename to packages/subgraph/human-protocol/tests/escrow-factory/escrow-factory.test.ts diff --git a/packages/sdk/typescript/subgraph/tests/escrow-factory/fixtures.ts b/packages/subgraph/human-protocol/tests/escrow-factory/fixtures.ts similarity index 100% rename from packages/sdk/typescript/subgraph/tests/escrow-factory/fixtures.ts rename to packages/subgraph/human-protocol/tests/escrow-factory/fixtures.ts diff --git a/packages/sdk/typescript/subgraph/tests/escrow/escrow.test.ts b/packages/subgraph/human-protocol/tests/escrow/escrow.test.ts similarity index 94% rename from packages/sdk/typescript/subgraph/tests/escrow/escrow.test.ts rename to packages/subgraph/human-protocol/tests/escrow/escrow.test.ts index 9fabb437ba..8dabd2bffb 100644 --- a/packages/sdk/typescript/subgraph/tests/escrow/escrow.test.ts +++ b/packages/subgraph/human-protocol/tests/escrow/escrow.test.ts @@ -31,6 +31,7 @@ import { handleIntermediateStorage, handlePending, handlePendingV2, + handlePendingV3, handleWithdraw, } from '../../src/mapping/Escrow'; import { toEventId } from '../../src/mapping/utils/event'; @@ -47,6 +48,7 @@ import { createISEvent, createPendingEvent, createPendingV2Event, + createPendingV3Event, createWithdrawEvent, } from './fixtures'; @@ -660,6 +662,26 @@ describe('Escrow', () => { assert.fieldEquals('FundEvent', id, 'sender', operatorAddressString); assert.fieldEquals('FundEvent', id, 'amount', '100'); + // Transaction + assert.fieldEquals( + 'Transaction', + fund.transaction.hash.toHex(), + 'method', + 'fund' + ); + assert.fieldEquals( + 'Transaction', + fund.transaction.hash.toHex(), + 'to', + escrowAddressString + ); + assert.fieldEquals( + 'Transaction', + fund.transaction.hash.toHex(), + 'value', + '100' + ); + // Escrow assert.fieldEquals('Escrow', escrowAddressString, 'balance', '100'); assert.fieldEquals( @@ -858,6 +880,58 @@ describe('Escrow', () => { ); }); + test('Should properly handle PendingV3 event with fee percentages from event', () => { + const URL = 'test-v3.com'; + const HASH = 'is_hash_v3_1'; + + const newPending1 = createPendingV3Event( + operatorAddress, + URL, + HASH, + reputationOracleAddress, + recordingOracleAddress, + exchangeOracleAddress, + 7, + 8, + 9, + BigInt.fromI32(51) + ); + + handlePendingV3(newPending1); + + const id = toEventId(newPending1).toHex(); + + assert.fieldEquals('PendingEvent', id, 'sender', operatorAddressString); + assert.fieldEquals('EscrowStatusEvent', id, 'status', 'Pending'); + assert.fieldEquals('Escrow', escrowAddress.toHex(), 'status', 'Pending'); + assert.fieldEquals('Escrow', escrowAddress.toHex(), 'manifest', URL); + assert.fieldEquals('Escrow', escrowAddress.toHex(), 'manifestHash', HASH); + assert.fieldEquals( + 'Escrow', + escrowAddress.toHex(), + 'reputationOracleFee', + '7' + ); + assert.fieldEquals( + 'Escrow', + escrowAddress.toHex(), + 'recordingOracleFee', + '8' + ); + assert.fieldEquals( + 'Escrow', + escrowAddress.toHex(), + 'exchangeOracleFee', + '9' + ); + assert.fieldEquals( + 'Transaction', + newPending1.transaction.hash.toHex(), + 'method', + 'setup' + ); + }); + test('should properly handle IntermediateStorage event', () => { const URL = 'test.com'; const HASH = 'is_hash_1'; @@ -1861,6 +1935,49 @@ describe('Escrow', () => { ); }); + test('Should properly calculate setup & pending in statistics for PendingV3', () => { + const newPending1 = createPendingV3Event( + operatorAddress, + 'test.com', + 'is_hash_1', + reputationOracleAddress, + recordingOracleAddress, + exchangeOracleAddress, + 5, + 5, + 5, + BigInt.fromI32(21) + ); + const newPending2 = createPendingV3Event( + operatorAddress, + 'test.com', + 'is_hash_2', + reputationOracleAddress, + recordingOracleAddress, + exchangeOracleAddress, + 10, + 10, + 10, + BigInt.fromI32(22) + ); + + handlePendingV3(newPending1); + handlePendingV3(newPending2); + + assert.fieldEquals( + 'EscrowStatistics', + STATISTICS_ENTITY_ID.toHex(), + 'pendingStatusEventCount', + '2' + ); + assert.fieldEquals( + 'EscrowStatistics', + STATISTICS_ENTITY_ID.toHex(), + 'totalEventCount', + '4' + ); + }); + test('Should properly calculate StoreResults event in statistics', () => { const newIS = createISEvent( workerAddress, diff --git a/packages/sdk/typescript/subgraph/tests/escrow/fixtures.ts b/packages/subgraph/human-protocol/tests/escrow/fixtures.ts similarity index 81% rename from packages/sdk/typescript/subgraph/tests/escrow/fixtures.ts rename to packages/subgraph/human-protocol/tests/escrow/fixtures.ts index 77d0b2020f..816f9b4412 100644 --- a/packages/sdk/typescript/subgraph/tests/escrow/fixtures.ts +++ b/packages/subgraph/human-protocol/tests/escrow/fixtures.ts @@ -12,6 +12,7 @@ import { IntermediateStorage, Pending, PendingV2, + PendingV3, Withdraw, } from '../../generated/templates/Escrow/Escrow'; import { generateUniqueHash } from '../../tests/utils'; @@ -98,6 +99,74 @@ export function createPendingV2Event( return newPendingEvent; } +export function createPendingV3Event( + sender: Address, + manifest: string, + hash: string, + reputationOracleAddress: Address, + recordingOracleAddress: Address, + exchangeOracleAddress: Address, + reputationOracleFeePercentage: i32, + recordingOracleFeePercentage: i32, + exchangeOracleFeePercentage: i32, + timestamp: BigInt +): PendingV3 { + const newPendingEvent = changetype(newMockEvent()); + newPendingEvent.transaction.hash = generateUniqueHash( + sender.toString(), + timestamp, + newPendingEvent.transaction.nonce + ); + + newPendingEvent.transaction.from = sender; + + newPendingEvent.parameters = []; + + const manifestParam = new ethereum.EventParam( + 'manifest', + ethereum.Value.fromString(manifest) + ); + const hashParam = new ethereum.EventParam( + 'hash', + ethereum.Value.fromString(hash) + ); + const reputationOracleAddressParam = new ethereum.EventParam( + 'reputationOracleAddress', + ethereum.Value.fromAddress(reputationOracleAddress) + ); + const recordingOracleAddressParam = new ethereum.EventParam( + 'recordingOracleAddress', + ethereum.Value.fromAddress(recordingOracleAddress) + ); + const exchangeOracleAddressParam = new ethereum.EventParam( + 'exchangeOracleAddress', + ethereum.Value.fromAddress(exchangeOracleAddress) + ); + const reputationOracleFeePercentageParam = new ethereum.EventParam( + 'reputationOracleFeePercentage', + ethereum.Value.fromI32(reputationOracleFeePercentage) + ); + const recordingOracleFeePercentageParam = new ethereum.EventParam( + 'recordingOracleFeePercentage', + ethereum.Value.fromI32(recordingOracleFeePercentage) + ); + const exchangeOracleFeePercentageParam = new ethereum.EventParam( + 'exchangeOracleFeePercentage', + ethereum.Value.fromI32(exchangeOracleFeePercentage) + ); + + newPendingEvent.parameters.push(manifestParam); + newPendingEvent.parameters.push(hashParam); + newPendingEvent.parameters.push(reputationOracleAddressParam); + newPendingEvent.parameters.push(recordingOracleAddressParam); + newPendingEvent.parameters.push(exchangeOracleAddressParam); + newPendingEvent.parameters.push(reputationOracleFeePercentageParam); + newPendingEvent.parameters.push(recordingOracleFeePercentageParam); + newPendingEvent.parameters.push(exchangeOracleFeePercentageParam); + + return newPendingEvent; +} + export function createISEvent( sender: Address, url: string, @@ -324,7 +393,7 @@ export function createFundEvent( ): Fund { const newFundEvent = changetype(newMockEvent()); newFundEvent.transaction.hash = generateUniqueHash( - sender.toString(), + sender.toString() + '-fund', timestamp, newFundEvent.transaction.nonce ); @@ -386,7 +455,7 @@ export function createCancellationRequestedEvent( ): CancellationRequested { const event = changetype(newMockEvent()); event.transaction.hash = generateUniqueHash( - sender.toString(), + sender.toString() + '-cancellation-requested', timestamp, event.transaction.nonce ); diff --git a/packages/sdk/typescript/subgraph/tests/kvstore/fixtures.ts b/packages/subgraph/human-protocol/tests/kvstore/fixtures.ts similarity index 100% rename from packages/sdk/typescript/subgraph/tests/kvstore/fixtures.ts rename to packages/subgraph/human-protocol/tests/kvstore/fixtures.ts diff --git a/packages/sdk/typescript/subgraph/tests/kvstore/kvstore.test.ts b/packages/subgraph/human-protocol/tests/kvstore/kvstore.test.ts similarity index 100% rename from packages/sdk/typescript/subgraph/tests/kvstore/kvstore.test.ts rename to packages/subgraph/human-protocol/tests/kvstore/kvstore.test.ts diff --git a/packages/sdk/typescript/subgraph/tests/legacy/escrow-factory/escrow-factory.test.ts b/packages/subgraph/human-protocol/tests/legacy/escrow-factory/escrow-factory.test.ts similarity index 100% rename from packages/sdk/typescript/subgraph/tests/legacy/escrow-factory/escrow-factory.test.ts rename to packages/subgraph/human-protocol/tests/legacy/escrow-factory/escrow-factory.test.ts diff --git a/packages/sdk/typescript/subgraph/tests/legacy/escrow-factory/fixtures.ts b/packages/subgraph/human-protocol/tests/legacy/escrow-factory/fixtures.ts similarity index 100% rename from packages/sdk/typescript/subgraph/tests/legacy/escrow-factory/fixtures.ts rename to packages/subgraph/human-protocol/tests/legacy/escrow-factory/fixtures.ts diff --git a/packages/sdk/typescript/subgraph/tests/legacy/escrow/escrow.test.ts b/packages/subgraph/human-protocol/tests/legacy/escrow/escrow.test.ts similarity index 100% rename from packages/sdk/typescript/subgraph/tests/legacy/escrow/escrow.test.ts rename to packages/subgraph/human-protocol/tests/legacy/escrow/escrow.test.ts diff --git a/packages/sdk/typescript/subgraph/tests/legacy/escrow/fixtures.ts b/packages/subgraph/human-protocol/tests/legacy/escrow/fixtures.ts similarity index 100% rename from packages/sdk/typescript/subgraph/tests/legacy/escrow/fixtures.ts rename to packages/subgraph/human-protocol/tests/legacy/escrow/fixtures.ts diff --git a/packages/sdk/typescript/subgraph/tests/staking/fixtures.ts b/packages/subgraph/human-protocol/tests/staking/fixtures.ts similarity index 100% rename from packages/sdk/typescript/subgraph/tests/staking/fixtures.ts rename to packages/subgraph/human-protocol/tests/staking/fixtures.ts diff --git a/packages/sdk/typescript/subgraph/tests/staking/staking.test.ts b/packages/subgraph/human-protocol/tests/staking/staking.test.ts similarity index 100% rename from packages/sdk/typescript/subgraph/tests/staking/staking.test.ts rename to packages/subgraph/human-protocol/tests/staking/staking.test.ts diff --git a/packages/sdk/typescript/subgraph/tests/utils.ts b/packages/subgraph/human-protocol/tests/utils.ts similarity index 100% rename from packages/sdk/typescript/subgraph/tests/utils.ts rename to packages/subgraph/human-protocol/tests/utils.ts diff --git a/packages/subgraph/human-protocol/tsconfig.json b/packages/subgraph/human-protocol/tsconfig.json new file mode 100644 index 0000000000..3c11a89fec --- /dev/null +++ b/packages/subgraph/human-protocol/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "baseUrl": ".", + "types": ["@graphprotocol/graph-ts", "node"] + }, + "include": ["src", "tests"] +} diff --git a/yarn.lock b/yarn.lock index 7f9cfc667a..7ba07907e7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4969,6 +4969,7 @@ __metadata: ts-node: "npm:^10.9.2" typechain: "npm:^8.3.2" typescript: "npm:^5.8.3" + typescript-eslint: "npm:^8.39.1" xdeployer: "npm:3.1.6" peerDependencies: ethers: ~6.15.0 @@ -11162,9 +11163,26 @@ __metadata: languageName: node linkType: hard -"@tools/subgraph@workspace:packages/sdk/typescript/subgraph": +"@tools/subgraph-hmt-stats@workspace:packages/subgraph/hmt": + version: 0.0.0-use.local + resolution: "@tools/subgraph-hmt-stats@workspace:packages/subgraph/hmt" + dependencies: + "@graphprotocol/graph-cli": "npm:^0.97.1" + "@graphprotocol/graph-ts": "npm:^0.38.0" + "@graphql-eslint/eslint-plugin": "npm:^3.19.1" + "@human-protocol/core": "workspace:*" + eslint: "npm:^9.39.1" + ethers: "npm:~6.15.0" + graphql: "npm:^16.6.0" + matchstick-as: "npm:^0.6.0" + mustache: "npm:^4.2.0" + prettier: "npm:^3.8.1" + languageName: unknown + linkType: soft + +"@tools/subgraph@workspace:packages/subgraph/human-protocol": version: 0.0.0-use.local - resolution: "@tools/subgraph@workspace:packages/sdk/typescript/subgraph" + resolution: "@tools/subgraph@workspace:packages/subgraph/human-protocol" dependencies: "@graphprotocol/graph-cli": "npm:^0.97.1" "@graphprotocol/graph-ts": "npm:^0.38.0"