diff --git a/packages/horizon/ignition/modules/core/HorizonStaking.ts b/packages/horizon/ignition/modules/core/HorizonStaking.ts index a7bec9076..0a13e9123 100644 --- a/packages/horizon/ignition/modules/core/HorizonStaking.ts +++ b/packages/horizon/ignition/modules/core/HorizonStaking.ts @@ -15,12 +15,12 @@ export default buildModule('HorizonStaking', (m) => { const subgraphServiceAddress = m.getParameter('subgraphServiceAddress') const maxThawingPeriod = m.getParameter('maxThawingPeriod') - // Deploy HorizonStaking implementation + // Deploy HorizonStaking implementation - requires periphery and proxies to be registered in the controller const HorizonStakingImplementation = deployImplementation(m, { name: 'HorizonStaking', artifact: HorizonStakingArtifact, constructorArgs: [Controller, subgraphServiceAddress], - }) + }, { after: [GraphPeripheryModule, HorizonProxiesModule] }) // Upgrade proxy to implementation contract const HorizonStaking = upgradeGraphProxy(m, GraphProxyAdmin, HorizonStakingProxy, HorizonStakingImplementation, { diff --git a/packages/interfaces/src/types/horizon.ts b/packages/interfaces/src/types/horizon.ts index c2a09abb6..1208dd11d 100644 --- a/packages/interfaces/src/types/horizon.ts +++ b/packages/interfaces/src/types/horizon.ts @@ -10,6 +10,7 @@ import type { IL2GNSToolshed, ILegacyRewardsManager, IPaymentsEscrowToolshed, + IRecurringCollector, IRewardsManagerToolshed, IStaking, ISubgraphNFT, @@ -29,5 +30,6 @@ export { IStaking as LegacyStaking, IPaymentsEscrowToolshed as PaymentsEscrow, IRewardsManagerToolshed as RewardsManager, + IRecurringCollector, ISubgraphNFT as SubgraphNFT, } diff --git a/packages/subgraph-service/ignition/configs/protocol.default.json5 b/packages/subgraph-service/ignition/configs/protocol.default.json5 index aedf53531..b48120ee7 100644 --- a/packages/subgraph-service/ignition/configs/protocol.default.json5 +++ b/packages/subgraph-service/ignition/configs/protocol.default.json5 @@ -23,7 +23,8 @@ "disputeManagerProxyAdminAddress": "", "subgraphServiceProxyAddress": "", "subgraphServiceProxyAdminAddress": "", - "graphTallyCollectorAddress": "" + "graphTallyCollectorAddress": "", + "recurringCollectorAddress": "" }, "DisputeManager": { "disputePeriod": 2419200, diff --git a/packages/subgraph-service/ignition/configs/protocol.localNetwork.json5 b/packages/subgraph-service/ignition/configs/protocol.localNetwork.json5 index 1b35b18c1..978e56a61 100644 --- a/packages/subgraph-service/ignition/configs/protocol.localNetwork.json5 +++ b/packages/subgraph-service/ignition/configs/protocol.localNetwork.json5 @@ -23,7 +23,8 @@ "disputeManagerProxyAdminAddress": "", "subgraphServiceProxyAddress": "", "subgraphServiceProxyAdminAddress": "", - "graphTallyCollectorAddress": "" + "graphTallyCollectorAddress": "", + "recurringCollectorAddress": "" }, "DisputeManager": { "disputePeriod": 7200, // 2 hours = 7200 seconds diff --git a/packages/subgraph-service/ignition/modules/SubgraphService.ts b/packages/subgraph-service/ignition/modules/SubgraphService.ts index 8efb6800b..5b52706ba 100644 --- a/packages/subgraph-service/ignition/modules/SubgraphService.ts +++ b/packages/subgraph-service/ignition/modules/SubgraphService.ts @@ -14,6 +14,7 @@ export default buildModule('SubgraphService', (m) => { const subgraphServiceProxyAdminAddress = m.getParameter('subgraphServiceProxyAdminAddress') const disputeManagerProxyAddress = m.getParameter('disputeManagerProxyAddress') const graphTallyCollectorAddress = m.getParameter('graphTallyCollectorAddress') + const recurringCollectorAddress = m.getParameter('recurringCollectorAddress') const curationProxyAddress = m.getParameter('curationProxyAddress') const minimumProvisionTokens = m.getParameter('minimumProvisionTokens') const maximumDelegationRatio = m.getParameter('maximumDelegationRatio') @@ -28,12 +29,52 @@ export default buildModule('SubgraphService', (m) => { subgraphServiceProxyAddress, ) - // Deploy implementation - const SubgraphServiceImplementation = deployImplementation(m, { - name: 'SubgraphService', - constructorArgs: [controllerAddress, disputeManagerProxyAddress, graphTallyCollectorAddress, curationProxyAddress], + // Deploy libraries required by SubgraphService's audit-branch constructor. + // The contract has been refactored to pull allocation, stake-claim, and + // indexing-agreement logic into stand-alone libraries; these must be + // deployed and link-passed to the implementation so the bytecode's + // placeholder slots resolve at deploy time. + // + // Ordering matters: IndexingAgreement calls into IndexingAgreementDecoder, + // which in turn calls into IndexingAgreementDecoderRaw, so the deeper + // libraries have to be deployed and linked first. + const StakeClaims = m.library('StakeClaims') + const AllocationHandler = m.library('AllocationHandler') + const IndexingAgreementDecoderRaw = m.library('IndexingAgreementDecoderRaw') + const IndexingAgreementDecoder = m.library('IndexingAgreementDecoder', { + libraries: { + IndexingAgreementDecoderRaw, + }, + }) + const IndexingAgreement = m.library('IndexingAgreement', { + libraries: { + IndexingAgreementDecoder, + }, }) + // Deploy implementation + const SubgraphServiceImplementation = deployImplementation( + m, + { + name: 'SubgraphService', + constructorArgs: [ + controllerAddress, + disputeManagerProxyAddress, + graphTallyCollectorAddress, + curationProxyAddress, + recurringCollectorAddress, + ], + }, + { + libraries: { + StakeClaims, + AllocationHandler, + IndexingAgreement, + IndexingAgreementDecoder, + }, + }, + ) + // Upgrade implementation const SubgraphService = upgradeTransparentUpgradeableProxy( m, diff --git a/packages/subgraph-service/tasks/deploy.ts b/packages/subgraph-service/tasks/deploy.ts index 581138439..860e8c67b 100644 --- a/packages/subgraph-service/tasks/deploy.ts +++ b/packages/subgraph-service/tasks/deploy.ts @@ -91,6 +91,7 @@ task('deploy:protocol', 'Deploy a new version of the Graph Protocol Horizon cont subgraphServiceProxyAddress: proxiesDeployment.Transparent_Proxy_SubgraphService.target as string, subgraphServiceProxyAdminAddress: proxiesDeployment.Transparent_ProxyAdmin_SubgraphService.target as string, graphTallyCollectorAddress: horizonDeployment.GraphTallyCollector.target as string, + recurringCollectorAddress: horizonDeployment.Transparent_Proxy_RecurringCollector.target as string, gnsProxyAddress: horizonDeployment.Graph_Proxy_L2GNS.target as string, gnsImplementationAddress: horizonDeployment.Implementation_L2GNS.target as string, subgraphNFTAddress: horizonDeployment.SubgraphNFT.target as string, diff --git a/packages/toolshed/src/core/index.ts b/packages/toolshed/src/core/index.ts index 3934ed378..7dbbb79ba 100644 --- a/packages/toolshed/src/core/index.ts +++ b/packages/toolshed/src/core/index.ts @@ -6,5 +6,6 @@ export * from './custom-errors' export * from './disputes' export * from './graph-tally' export * from './poi' +export * from './recurring-collector' export * from './subgraph-service' export * from './types' diff --git a/packages/toolshed/src/core/recurring-collector.ts b/packages/toolshed/src/core/recurring-collector.ts new file mode 100644 index 000000000..9be27f44b --- /dev/null +++ b/packages/toolshed/src/core/recurring-collector.ts @@ -0,0 +1,85 @@ +import { BytesLike, ethers } from 'ethers' + +// -- ABI tuple types for decoding -- + +const RCA_TUPLE = + 'tuple(uint64 deadline, uint64 endsAt, address payer, address dataService, address serviceProvider, uint256 maxInitialTokens, uint256 maxOngoingTokensPerSecond, uint32 minSecondsPerCollection, uint32 maxSecondsPerCollection, uint16 conditions, uint256 nonce, bytes metadata)' + +const SIGNED_RCA_TUPLE = `tuple(${RCA_TUPLE} rca, bytes signature)` + +const ACCEPT_METADATA_TUPLE = 'tuple(bytes32 subgraphDeploymentId, uint8 version, bytes terms)' + +const TERMS_V1_TUPLE = 'tuple(uint256 tokensPerSecond, uint256 tokensPerEntityPerSecond)' + +// -- Return types -- + +export interface RecurringCollectionAgreement { + deadline: bigint + endsAt: bigint + payer: string + dataService: string + serviceProvider: string + maxInitialTokens: bigint + maxOngoingTokensPerSecond: bigint + minSecondsPerCollection: bigint + maxSecondsPerCollection: bigint + conditions: bigint + nonce: bigint + metadata: string +} + +export interface SignedRCA { + rca: RecurringCollectionAgreement + signature: string +} + +export interface AcceptIndexingAgreementMetadata { + subgraphDeploymentId: string + version: bigint + terms: string +} + +export interface IndexingAgreementTermsV1 { + tokensPerSecond: bigint + tokensPerEntityPerSecond: bigint +} + +// -- Decoders -- + +export function decodeSignedRCA(data: BytesLike): SignedRCA { + const [decoded] = ethers.AbiCoder.defaultAbiCoder().decode([SIGNED_RCA_TUPLE], data) + return { + rca: { + deadline: decoded.rca.deadline, + endsAt: decoded.rca.endsAt, + payer: decoded.rca.payer, + dataService: decoded.rca.dataService, + serviceProvider: decoded.rca.serviceProvider, + maxInitialTokens: decoded.rca.maxInitialTokens, + maxOngoingTokensPerSecond: decoded.rca.maxOngoingTokensPerSecond, + minSecondsPerCollection: decoded.rca.minSecondsPerCollection, + maxSecondsPerCollection: decoded.rca.maxSecondsPerCollection, + conditions: decoded.rca.conditions, + nonce: decoded.rca.nonce, + metadata: decoded.rca.metadata, + }, + signature: decoded.signature, + } +} + +export function decodeAcceptIndexingAgreementMetadata(data: BytesLike): AcceptIndexingAgreementMetadata { + const [decoded] = ethers.AbiCoder.defaultAbiCoder().decode([ACCEPT_METADATA_TUPLE], data) + return { + subgraphDeploymentId: decoded.subgraphDeploymentId, + version: decoded.version, + terms: decoded.terms, + } +} + +export function decodeIndexingAgreementTermsV1(data: BytesLike): IndexingAgreementTermsV1 { + const [decoded] = ethers.AbiCoder.defaultAbiCoder().decode([TERMS_V1_TUPLE], data) + return { + tokensPerSecond: decoded.tokensPerSecond, + tokensPerEntityPerSecond: decoded.tokensPerEntityPerSecond, + } +} diff --git a/packages/toolshed/src/core/subgraph-service.ts b/packages/toolshed/src/core/subgraph-service.ts index b4301900f..03a7840d0 100644 --- a/packages/toolshed/src/core/subgraph-service.ts +++ b/packages/toolshed/src/core/subgraph-service.ts @@ -32,6 +32,21 @@ export function encodeCollectQueryFeesData(rav: RAV, signature: string, tokensTo ) } +export function encodeCollectIndexingFeesData( + agreementId: string, + entities: bigint, + poi: BytesLike, + poiBlockNumber: bigint, + metadata: BytesLike, + maxSlippage: bigint, +) { + const innerData = ethers.AbiCoder.defaultAbiCoder().encode( + ['uint256', 'bytes32', 'uint256', 'bytes', 'uint256'], + [entities, poi, poiBlockNumber, metadata, maxSlippage], + ) + return ethers.AbiCoder.defaultAbiCoder().encode(['bytes16', 'bytes'], [agreementId, innerData]) +} + export function encodeStopServiceData(allocationId: string) { return ethers.AbiCoder.defaultAbiCoder().encode(['address'], [allocationId]) } diff --git a/packages/toolshed/src/deployments/horizon/contracts.ts b/packages/toolshed/src/deployments/horizon/contracts.ts index bd852d5f0..4221d9694 100644 --- a/packages/toolshed/src/deployments/horizon/contracts.ts +++ b/packages/toolshed/src/deployments/horizon/contracts.ts @@ -5,6 +5,7 @@ import type { GraphProxyAdmin, GraphTallyCollector, HorizonStaking, + IRecurringCollector, L2Curation, L2GNS, L2GraphToken, @@ -36,6 +37,7 @@ export const GraphHorizonContractNameList = [ 'GraphPayments', 'PaymentsEscrow', 'GraphTallyCollector', + 'RecurringCollector', ] as const export interface GraphHorizonContracts extends ContractList { @@ -56,6 +58,7 @@ export interface GraphHorizonContracts extends ContractList