Skip to content

Commit 959dde7

Browse files
committed
chore: added subgraph-service integration tests
1 parent 4fa063d commit 959dde7

File tree

23 files changed

+29889
-8
lines changed

23 files changed

+29889
-8
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { BytesLike, HDNodeWallet, Interface } from 'ethers'
2+
import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers'
3+
4+
import { ISubgraphService } from '@graphprotocol/subgraph-service'
5+
6+
import { PaymentTypes } from '../../horizon/utils/types'
7+
8+
/* //////////////////////////////////////////////////////////////
9+
EXPORTS
10+
////////////////////////////////////////////////////////////// */
11+
12+
export const SubgraphServiceActions = {
13+
collect,
14+
migrateLegacyAllocation,
15+
register,
16+
startService,
17+
}
18+
19+
/* //////////////////////////////////////////////////////////////
20+
REGISTRATION
21+
////////////////////////////////////////////////////////////// */
22+
23+
interface RegisterParams {
24+
subgraphService: ISubgraphService
25+
indexer: HardhatEthersSigner
26+
data: BytesLike
27+
}
28+
29+
/**
30+
* Registers an indexer with the subgraph service
31+
* @param subgraphService The subgraph service contract
32+
* @param indexer The indexer that is registering
33+
* @param data The encoded registration data
34+
*/
35+
export async function register({
36+
subgraphService,
37+
indexer,
38+
data,
39+
}: RegisterParams) {
40+
const tx = await subgraphService.connect(indexer).register(indexer.address, data)
41+
await tx.wait()
42+
}
43+
44+
/* //////////////////////////////////////////////////////////////
45+
ALLOCATION MANAGEMENT
46+
////////////////////////////////////////////////////////////// */
47+
48+
interface MigrateLegacyAllocationParams {
49+
subgraphService: ISubgraphService
50+
governor: HardhatEthersSigner
51+
indexer: string
52+
allocationId: string
53+
subgraphDeploymentId: string
54+
}
55+
56+
/**
57+
* Migrates a legacy allocation from the old staking contract to the new subgraph service
58+
* @param subgraphService The subgraph service contract
59+
* @param governor The governor of the subgraph service
60+
* @param indexer The indexer owner of the legacy allocation
61+
* @param allocationId The allocation id of the legacy allocation
62+
* @param subgraphDeploymentId The subgraph deployment id for the legacy allocation
63+
*/
64+
export async function migrateLegacyAllocation({
65+
subgraphService,
66+
governor,
67+
indexer,
68+
allocationId,
69+
subgraphDeploymentId,
70+
}: MigrateLegacyAllocationParams) {
71+
const tx = await subgraphService.connect(governor).migrateLegacyAllocation(indexer, allocationId, subgraphDeploymentId)
72+
await tx.wait()
73+
}
74+
75+
interface StartServiceParams {
76+
subgraphService: ISubgraphService
77+
indexer: HardhatEthersSigner
78+
data: BytesLike
79+
}
80+
81+
/**
82+
* Service provider starts providing service for a subgraph deployment
83+
* @param subgraphService The subgraph service contract
84+
* @param indexer The indexer that is starting the service
85+
* @param data The encoded data for the allocation
86+
*/
87+
export async function startService({
88+
subgraphService,
89+
indexer,
90+
data,
91+
}: StartServiceParams) {
92+
const tx = await subgraphService.connect(indexer).startService(indexer.address, data)
93+
await tx.wait()
94+
}
95+
96+
/* //////////////////////////////////////////////////////////////
97+
COLLECT
98+
////////////////////////////////////////////////////////////// */
99+
100+
interface CollectParams {
101+
subgraphService: ISubgraphService
102+
signer: HardhatEthersSigner | HDNodeWallet
103+
indexer: string
104+
paymentType: PaymentTypes
105+
data: BytesLike
106+
}
107+
108+
/**
109+
* Collects the allocated funds for a subgraph deployment
110+
* @param subgraphService The subgraph service contract
111+
* @param indexer The indexer that is collecting the funds
112+
* @param data The encoded data for the allocation
113+
* @returns The payment collected
114+
*/
115+
export async function collect({ subgraphService, signer, indexer, paymentType, data }: CollectParams): Promise<bigint> {
116+
const tx = await subgraphService.connect(signer).collect(
117+
indexer,
118+
paymentType,
119+
data
120+
)
121+
const receipt = await tx.wait()
122+
if (!receipt) throw new Error('Transaction failed')
123+
124+
const iface = new Interface(['event ServicePaymentCollected(address indexed serviceProvider, uint8 indexed feeType, uint256 tokens)'])
125+
const event = receipt.logs.find(log => log.topics[0] === iface.getEvent('ServicePaymentCollected')?.topicHash)
126+
if (!event) throw new Error('ServicePaymentCollected event not found')
127+
128+
return BigInt(event.data)
129+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { Wallet, Signature } from 'ethers'
2+
3+
import { ISubgraphService } from '@graphprotocol/subgraph-service'
4+
5+
/**
6+
* Generates an allocation proof
7+
* @param subgraphService The subgraph service contract
8+
* @param indexerAddress The address of the indexer
9+
* @param allocationPrivateKey The private key of the allocation
10+
* @returns The encoded allocation proof
11+
*/
12+
export async function generateAllocationProof(
13+
subgraphService: ISubgraphService,
14+
indexerAddress: string,
15+
allocationPrivateKey: string,
16+
): Promise<string> {
17+
const wallet = new Wallet(allocationPrivateKey)
18+
const messageHash = await subgraphService.encodeAllocationProof(indexerAddress, wallet.address)
19+
const signature = wallet.signingKey.sign(messageHash)
20+
return Signature.from(signature).serialized
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { BytesLike, ethers, HDNodeWallet, Signature } from 'ethers'
2+
3+
import { IGraphTallyCollector } from '@graphprotocol/subgraph-service'
4+
5+
/**
6+
* Generates a signed RAV calldata
7+
* @param graphTallyCollector The Graph Tally Collector contract
8+
* @param signer The signer
9+
* @param collectionId The collection ID
10+
* @param payer The payer
11+
* @param serviceProvider The service provider
12+
* @param dataService The data service
13+
* @param timestampNs The timestamp in nanoseconds
14+
* @param valueAggregate The value aggregate
15+
* @param metadata The metadata
16+
* @returns The encoded signed RAV calldata
17+
*/
18+
export async function getSignedRAVCalldata(
19+
graphTallyCollector: IGraphTallyCollector,
20+
signer: HDNodeWallet,
21+
allocationId: string,
22+
payer: string,
23+
serviceProvider: string,
24+
dataService: string,
25+
timestampNs: number,
26+
valueAggregate: bigint,
27+
metadata: BytesLike
28+
) {
29+
const ravData = {
30+
collectionId: ethers.zeroPadValue(allocationId, 32),
31+
payer: payer,
32+
serviceProvider: serviceProvider,
33+
dataService: dataService,
34+
timestampNs: timestampNs,
35+
valueAggregate: valueAggregate,
36+
metadata: metadata
37+
}
38+
39+
const encodedRAV = await graphTallyCollector.encodeRAV(ravData)
40+
const messageHash = ethers.getBytes(encodedRAV)
41+
const signature = ethers.Signature.from(signer.signingKey.sign(messageHash)).serialized
42+
const signedRAV = { rav: ravData, signature }
43+
return ethers.AbiCoder.defaultAbiCoder().encode(
44+
['tuple(tuple(bytes32 collectionId, address payer, address serviceProvider, address dataService, uint256 timestampNs, uint128 valueAggregate, bytes metadata) rav, bytes signature)'],
45+
[signedRAV]
46+
)
47+
}
48+
49+
/**
50+
* Generates a signer proof for authorizing a signer in the Graph Tally Collector
51+
* @param graphTallyCollector The Graph Tally Collector contract
52+
* @param signer The signer
53+
* @param chainId The chain ID
54+
* @param proofDeadline The deadline for the proof
55+
* @param signerPrivateKey The private key of the signer
56+
* @returns The encoded signer proof
57+
*/
58+
export async function getSignerProof(
59+
graphTallyCollector: IGraphTallyCollector,
60+
signer: HDNodeWallet,
61+
chainId: bigint,
62+
proofDeadline: bigint,
63+
payer: string
64+
): Promise<string> {
65+
// Create the message hash
66+
const messageHash = ethers.keccak256(
67+
ethers.solidityPacked(
68+
['uint256', 'address', 'string', 'uint256', 'address'],
69+
[
70+
chainId,
71+
await graphTallyCollector.getAddress(),
72+
'authorizeSignerProof',
73+
proofDeadline,
74+
payer
75+
]
76+
)
77+
)
78+
79+
// Convert to EIP-191 signed message hash (this is the proofToDigest)
80+
const proofToDigest = ethers.hashMessage(ethers.getBytes(messageHash))
81+
82+
// Sign the message
83+
return Signature.from(signer.signingKey.sign(proofToDigest)).serialized
84+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { generateAllocationProof } from './deployments/subgraph-service/utils/allocation'
2+
import { getSignedRAVCalldata, getSignerProof } from './deployments/subgraph-service/utils/collection'
3+
import { SubgraphServiceActions } from './deployments/subgraph-service/actions/subgraphService'
4+
5+
export {
6+
generateAllocationProof,
7+
getSignedRAVCalldata,
8+
getSignerProof,
9+
SubgraphServiceActions,
10+
}

packages/horizon/tasks/test/fixtures/indexers.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ const INDEXER_TWO_SECOND_ALLOCATION_PRIVATE_KEY = '0xab6cb9dbb3646a856e6cac2c0e2
3838

3939
// Indexer three data
4040
const INDEXER_THREE_ADDRESS = '0x28a8746e75304c0780E011BEd21C72cD78cd535E' // Hardhat account #6
41-
41+
const INDEXER_THREE_REWARDS_DESTINATION = '0xA3D22DDf431A8745888804F520D4eA51Cb43A458'
4242
// Subgraph deployment IDs
4343
const SUBGRAPH_DEPLOYMENT_ID_ONE = '0x02cd85012c1f075fd58fad178fd23ab841d3b5ddcf5cd3377c30118da97cb2a4'
4444
const SUBGRAPH_DEPLOYMENT_ID_TWO = '0x03ca89485a59894f1acfa34660c69024b6b90ce45171dece7662b0886bc375c7'
@@ -47,7 +47,7 @@ const SUBGRAPH_DEPLOYMENT_ID_THREE = '0x0472e8c46f728adb65a22187c6740532f82c2eba
4747
export const indexers: Indexer[] = [
4848
{
4949
address: INDEXER_ONE_ADDRESS,
50-
stake: parseEther('1000000'),
50+
stake: parseEther('1100000'),
5151
tokensToUnstake: parseEther('10000'),
5252
indexingRewardCut: 900000, // 90%
5353
queryFeeCut: 900000, // 90%
@@ -74,7 +74,7 @@ export const indexers: Indexer[] = [
7474
},
7575
{
7676
address: INDEXER_TWO_ADDRESS,
77-
stake: parseEther('1000000'),
77+
stake: parseEther('1100000'),
7878
tokensToUnstake: parseEther('1000000'),
7979
indexingRewardCut: 850000, // 85%
8080
queryFeeCut: 850000, // 85%
@@ -96,9 +96,10 @@ export const indexers: Indexer[] = [
9696
},
9797
{
9898
address: INDEXER_THREE_ADDRESS,
99-
stake: parseEther('1000000'),
99+
stake: parseEther('1100000'),
100100
indexingRewardCut: 800000, // 80%
101101
queryFeeCut: 800000, // 80%
102+
rewardsDestination: INDEXER_THREE_REWARDS_DESTINATION,
102103
allocations: [],
103104
},
104105
]

packages/subgraph-service/hardhat.config.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ const config: HardhatUserConfig = {
2222
settings: {
2323
optimizer: {
2424
enabled: true,
25-
runs: 50,
25+
runs: 1,
2626
},
2727
},
2828
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"$global": {
3+
// Accounts for new deployment - derived from local network mnemonic
4+
"governor": "0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0",
5+
"arbitrator": "0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b",
6+
"pauseGuardian": "0xE11BA2b4D45Eaed5996Cd0823791E0C93114882d",
7+
8+
// Addresses for contracts deployed in the original Graph Protocol - Arbitrum Sepolia values
9+
"controllerAddress": "0x9DB3ee191681f092607035d9BDA6e59FbEaCa695",
10+
"curationProxyAddress": "0xDe761f075200E75485F4358978FB4d1dC8644FD5",
11+
"curationImplementationAddress": "0xd90022aB67920212D0F902F5c427DE82732DE136",
12+
13+
// Must be set for step 2 of the deployment
14+
"disputeManagerProxyAddress": "",
15+
"disputeManagerProxyAdminAddress": "",
16+
"subgraphServiceProxyAddress": "",
17+
"subgraphServiceProxyAdminAddress": "",
18+
"graphTallyCollectorAddress": ""
19+
},
20+
"DisputeManager": {
21+
"disputePeriod": 2419200,
22+
"disputeDeposit": "10000000000000000000000n",
23+
"fishermanRewardCut": 500000,
24+
"maxSlashingCut": 1000000,
25+
},
26+
"SubgraphService": {
27+
"minimumProvisionTokens": "100000000000000000000000n",
28+
"maximumDelegationRatio": 16,
29+
"stakeToFeesRatio": 2,
30+
"maxPOIStaleness": 2419200, // 28 days = 2419200 seconds
31+
"curationCut": 100000,
32+
}
33+
}

packages/subgraph-service/package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
"clean": "rm -rf build dist cache cache_forge typechain-types",
2121
"build": "hardhat compile",
2222
"test": "forge test",
23-
"test:deployment": "SECURE_ACCOUNTS_DISABLE_PROVIDER=true hardhat test"
23+
"test:deployment": "SECURE_ACCOUNTS_DISABLE_PROVIDER=true hardhat test",
24+
"test:integration": "./scripts/test/integration"
2425
},
2526
"devDependencies": {
2627
"@defi-wonderland/natspec-smells": "^1.1.6",
@@ -47,6 +48,7 @@
4748
"eslint": "^8.56.0",
4849
"eslint-graph-config": "workspace:^0.0.1",
4950
"ethers": "^6.13.4",
51+
"glob": "^11.0.1",
5052
"hardhat": "^2.22.18",
5153
"hardhat-contract-sizer": "^2.10.0",
5254
"hardhat-gas-reporter": "^1.0.8",

0 commit comments

Comments
 (0)