diff --git a/subgraph/src/pdp-verifier.ts b/subgraph/src/pdp-verifier.ts index a69691c..127a886 100644 --- a/subgraph/src/pdp-verifier.ts +++ b/subgraph/src/pdp-verifier.ts @@ -1,4 +1,4 @@ -import { BigInt, Bytes, log, store, Value } from "@graphprotocol/graph-ts"; +import { Address, BigInt, Bytes, log, store } from "@graphprotocol/graph-ts"; import { NextProvingPeriod as NextProvingPeriodEvent, PossessionProven as PossessionProvenEvent, @@ -60,10 +60,74 @@ function getEventLogEntityId(txHash: Bytes, logIndex: BigInt): Bytes { // ----------------------------------------- +class ListenerAddrResult { + constructor( + public addr: Address, + public method: string + ) {} +} + +// DataSetCreated is emitted by two entry points: +// createDataSet(address listenerAddr, bytes extraData) selector 0xbbae41cb +// → listenerAddr is param 0, ABI slot at input[4..36], address bytes at input[16..36] +// addPieces(uint256 setId, address listenerAddr, ...) selector 0x9afd37f2 +// → listenerAddr is param 1, ABI slot at input[36..68], address bytes at input[48..68] +function decodeListenerAddrFromInput(input: Bytes): ListenerAddrResult { + if (input.length < 4) { + log.warning("decodeListenerAddrFromInput: input too short ({})", [ + input.length.toString(), + ]); + return new ListenerAddrResult(Address.zero(), "unknown"); + } + + if ( + input[0] == 0xbb && + input[1] == 0xae && + input[2] == 0x41 && + input[3] == 0xcb + ) { + if (input.length < 36) { + log.warning( + "decodeListenerAddrFromInput: createDataSet input too short ({})", + [input.length.toString()] + ); + return new ListenerAddrResult(Address.zero(), "createDataSet"); + } + return new ListenerAddrResult( + Address.fromBytes(Bytes.fromUint8Array(input.slice(16, 36))), + "createDataSet" + ); + } + + if ( + input[0] == 0x9a && + input[1] == 0xfd && + input[2] == 0x37 && + input[3] == 0xf2 + ) { + if (input.length < 68) { + log.warning( + "decodeListenerAddrFromInput: addPieces input too short ({})", + [input.length.toString()] + ); + return new ListenerAddrResult(Address.zero(), "addPieces"); + } + return new ListenerAddrResult( + Address.fromBytes(Bytes.fromUint8Array(input.slice(48, 68))), + "addPieces" + ); + } + + const funcSelector = Bytes.fromUint8Array(input.slice(0, 4)); + log.warning("decodeListenerAddrFromInput: unknown function selector {}", [ + funcSelector.toHexString(), + ]); + return new ListenerAddrResult(Address.zero(), "unknown"); +} + export function handleDataSetCreated(event: DataSetCreatedEvent): void { - const listenerAddr = Bytes.fromUint8Array( - event.transaction.input.subarray(16, 36) - ); + const decoded = decodeListenerAddrFromInput(event.transaction.input); + const listenerAddr = decoded.addr; const proofSetEntityId = getProofSetEntityId(event.params.setId); const transactionEntityId = getTransactionEntityId(event.transaction.hash); @@ -99,7 +163,7 @@ export function handleDataSetCreated(event: DataSetCreatedEvent): void { transaction.fromAddress = event.transaction.from; transaction.toAddress = event.transaction.to; // Can be null for contract creation transaction.value = event.transaction.value; - transaction.method = "createDataSet"; // Or derive from input data if possible + transaction.method = decoded.method; transaction.status = true; // Assuming success if event emitted transaction.createdAt = event.block.timestamp; // Link entities diff --git a/subgraph/tests/dataset-status.test.ts b/subgraph/tests/dataset-status.test.ts index 0f95674..0c703fa 100644 --- a/subgraph/tests/dataset-status.test.ts +++ b/subgraph/tests/dataset-status.test.ts @@ -42,7 +42,6 @@ describe("DataSetStatus Lifecycle Tests", () => { let mockDataSetCreatedEvent = createDataSetCreatedEvent( SET_ID, SENDER_ADDRESS, - Bytes.fromI32(123), CONTRACT_ADDRESS, BigInt.fromI32(100), BigInt.fromI32(1678886400), @@ -62,7 +61,6 @@ describe("DataSetStatus Lifecycle Tests", () => { let mockDataSetCreatedEvent = createDataSetCreatedEvent( SET_ID, SENDER_ADDRESS, - Bytes.fromI32(123), CONTRACT_ADDRESS, BigInt.fromI32(100), BigInt.fromI32(1678886400), @@ -96,7 +94,6 @@ describe("DataSetStatus Lifecycle Tests", () => { let mockDataSetCreatedEvent = createDataSetCreatedEvent( SET_ID, SENDER_ADDRESS, - Bytes.fromI32(123), CONTRACT_ADDRESS, BigInt.fromI32(100), BigInt.fromI32(1678886400), @@ -143,7 +140,6 @@ describe("DataSetStatus Lifecycle Tests", () => { let mockDataSetCreatedEvent = createDataSetCreatedEvent( SET_ID, SENDER_ADDRESS, - Bytes.fromI32(123), CONTRACT_ADDRESS, BigInt.fromI32(100), BigInt.fromI32(1678886400), @@ -190,7 +186,6 @@ describe("DataSetStatus Lifecycle Tests", () => { let mockDataSetCreatedEvent = createDataSetCreatedEvent( SET_ID, SENDER_ADDRESS, - Bytes.fromI32(123), CONTRACT_ADDRESS, BigInt.fromI32(100), BigInt.fromI32(1678886400), @@ -236,7 +231,6 @@ describe("DataSetStatus Lifecycle Tests", () => { let mockDataSetCreatedEvent = createDataSetCreatedEvent( SET_ID, SENDER_ADDRESS, - Bytes.fromI32(123), CONTRACT_ADDRESS, BigInt.fromI32(100), BigInt.fromI32(1678886400), @@ -293,7 +287,6 @@ describe("DataSetStatus Lifecycle Tests", () => { let mockDataSetCreatedEvent = createDataSetCreatedEvent( SET_ID, SENDER_ADDRESS, - Bytes.fromI32(123), CONTRACT_ADDRESS, BigInt.fromI32(100), BigInt.fromI32(1678886400), @@ -350,7 +343,6 @@ describe("DataSetStatus Lifecycle Tests", () => { let mockDataSetCreatedEvent = createDataSetCreatedEvent( SET_ID, SENDER_ADDRESS, - Bytes.fromI32(123), CONTRACT_ADDRESS, BigInt.fromI32(100), BigInt.fromI32(1678886400), @@ -370,7 +362,6 @@ describe("DataSetStatus Lifecycle Tests", () => { let mockDataSetCreatedEvent = createDataSetCreatedEvent( SET_ID, SENDER_ADDRESS, - Bytes.fromI32(123), CONTRACT_ADDRESS, BigInt.fromI32(100), BigInt.fromI32(1678886400), @@ -416,7 +407,6 @@ describe("DataSetStatus Lifecycle Tests", () => { let mockDataSetCreatedEvent = createDataSetCreatedEvent( SET_ID, SENDER_ADDRESS, - Bytes.fromI32(123), CONTRACT_ADDRESS, BigInt.fromI32(100), BigInt.fromI32(1678886400), diff --git a/subgraph/tests/fault-calculation.test.ts b/subgraph/tests/fault-calculation.test.ts index 6e4acd5..b54df2e 100644 --- a/subgraph/tests/fault-calculation.test.ts +++ b/subgraph/tests/fault-calculation.test.ts @@ -81,11 +81,11 @@ describe("Fault Calculation Tests", () => { SET_ID, PROVIDER_ADDRESS, CONTRACT_ADDRESS, - LISTENER_ADDRESS, blockNumber, timestamp, generateTxHash(100), - BigInt.fromI32(0) + BigInt.fromI32(0), + LISTENER_ADDRESS ); handleDataSetCreated(dataSetCreatedEvent); @@ -121,11 +121,11 @@ describe("Fault Calculation Tests", () => { SET_ID, PROVIDER_ADDRESS, CONTRACT_ADDRESS, - LISTENER_ADDRESS, createBlockNumber, createTimestamp, generateTxHash(200), - BigInt.fromI32(0) + BigInt.fromI32(0), + LISTENER_ADDRESS ); handleDataSetCreated(dataSetCreatedEvent); addRootToDataSet(SET_ID, ROOT_ID_1); @@ -231,11 +231,11 @@ describe("Fault Calculation Tests", () => { SET_ID, PROVIDER_ADDRESS, CONTRACT_ADDRESS, - LISTENER_ADDRESS, createBlockNumber, BigInt.fromI32(1000), generateTxHash(300), - BigInt.fromI32(0) + BigInt.fromI32(0), + LISTENER_ADDRESS ); handleDataSetCreated(dataSetCreatedEvent); addRootToDataSet(SET_ID, ROOT_ID_1); @@ -312,11 +312,11 @@ describe("Fault Calculation Tests", () => { SET_ID, PROVIDER_ADDRESS, CONTRACT_ADDRESS, - LISTENER_ADDRESS, createBlockNumber, BigInt.fromI32(1000), generateTxHash(400), - BigInt.fromI32(0) + BigInt.fromI32(0), + LISTENER_ADDRESS ); handleDataSetCreated(dataSetCreatedEvent); addRootToDataSet(SET_ID, ROOT_ID_1); @@ -376,11 +376,11 @@ describe("Fault Calculation Tests", () => { SET_ID, PROVIDER_ADDRESS, CONTRACT_ADDRESS, - LISTENER_ADDRESS, createBlockNumber, BigInt.fromI32(1000), generateTxHash(500), - BigInt.fromI32(0) + BigInt.fromI32(0), + LISTENER_ADDRESS ); handleDataSetCreated(dataSetCreatedEvent); addRootToDataSet(SET_ID, ROOT_ID_1); @@ -458,11 +458,11 @@ describe("Fault Calculation Tests", () => { SET_ID, PROVIDER_ADDRESS, CONTRACT_ADDRESS, - LISTENER_ADDRESS, createBlockNumber, BigInt.fromI32(1000), generateTxHash(600), - BigInt.fromI32(0) + BigInt.fromI32(0), + LISTENER_ADDRESS ); handleDataSetCreated(dataSetCreatedEvent); addRootToDataSet(SET_ID, ROOT_ID_1); @@ -549,11 +549,11 @@ describe("Fault Calculation Tests", () => { SET_ID, PROVIDER_ADDRESS, CONTRACT_ADDRESS, - LISTENER_ADDRESS, createBlockNumber, BigInt.fromI32(1000), generateTxHash(700), - BigInt.fromI32(0) + BigInt.fromI32(0), + LISTENER_ADDRESS ); handleDataSetCreated(dataSetCreatedEvent); addRootToDataSet(SET_ID, ROOT_ID_1); @@ -624,11 +624,11 @@ describe("Fault Calculation Tests", () => { SET_ID, PROVIDER_ADDRESS, CONTRACT_ADDRESS, - LISTENER_ADDRESS, createBlockNumber, BigInt.fromI32(1000), generateTxHash(800), - BigInt.fromI32(0) + BigInt.fromI32(0), + LISTENER_ADDRESS ); handleDataSetCreated(dataSetCreatedEvent); addRootToDataSet(SET_ID, ROOT_ID_1); @@ -699,11 +699,11 @@ describe("Fault Calculation Tests", () => { SET_ID, PROVIDER_ADDRESS, CONTRACT_ADDRESS, - LISTENER_ADDRESS, createBlockNumber, BigInt.fromI32(1000), generateTxHash(900), - BigInt.fromI32(0) + BigInt.fromI32(0), + LISTENER_ADDRESS ); handleDataSetCreated(dataSetCreatedEvent); addRootToDataSet(SET_ID, ROOT_ID_1); @@ -862,11 +862,11 @@ describe("Fault Calculation Tests", () => { SET_ID, PROVIDER_ADDRESS, CONTRACT_ADDRESS, - LISTENER_ADDRESS, createBlockNumber, BigInt.fromI32(1000), generateTxHash(1000), - BigInt.fromI32(0) + BigInt.fromI32(0), + LISTENER_ADDRESS ); handleDataSetCreated(dataSetCreatedEvent); addRootToDataSet(SET_ID, ROOT_ID_1); diff --git a/subgraph/tests/pdp-verifier-utils.ts b/subgraph/tests/pdp-verifier-utils.ts index d3e37a2..ad7b674 100644 --- a/subgraph/tests/pdp-verifier-utils.ts +++ b/subgraph/tests/pdp-verifier-utils.ts @@ -15,17 +15,18 @@ export function generateTxHash(counter: i32): Bytes { return Bytes.fromHexString("0x" + hexCounter); } -// Mocks the DataSetCreated event -// event DataSetCreated(uint256 indexed setId, address indexed provider, bytes32 root); +// Mocks the DataSetCreated event triggered by a direct createDataSet() call. +// Builds the transaction input for: createDataSet(address listenerAddr, bytes extraData) +// selector 0xbbae41cb — listenerAddr occupies input[16:36]. export function createDataSetCreatedEvent( setId: BigInt, provider: Address, - root: Bytes, // Although root is part of the event, handleDataSetCreated might not use it directly contractAddress: Address, blockNumber: BigInt = BigInt.fromI32(1), timestamp: BigInt = BigInt.fromI32(1), txHash: Bytes = generateTxHash(1), - logIndex: BigInt = BigInt.fromI32(0) + logIndex: BigInt = BigInt.fromI32(0), + listenerAddr: Address = Address.zero() ): DataSetCreated { let DataSetCreatedEvent = changetype(newMockEvent()); @@ -36,26 +37,95 @@ export function createDataSetCreatedEvent( ethereum.Value.fromUnsignedBigInt(setId) ); let providerParam = new ethereum.EventParam( - "provider", + "storageProvider", ethereum.Value.fromAddress(provider) ); - let rootParam = new ethereum.EventParam( - "root", - ethereum.Value.fromFixedBytes(root) - ); DataSetCreatedEvent.parameters.push(setIdParam); DataSetCreatedEvent.parameters.push(providerParam); - DataSetCreatedEvent.parameters.push(rootParam); DataSetCreatedEvent.address = contractAddress; DataSetCreatedEvent.block.number = blockNumber; DataSetCreatedEvent.block.timestamp = timestamp; DataSetCreatedEvent.transaction.hash = txHash; DataSetCreatedEvent.logIndex = logIndex; + DataSetCreatedEvent.transaction.from = provider; + DataSetCreatedEvent.transaction.to = contractAddress; + + // Build createDataSet(address,bytes) calldata: + // [0..3] selector 0xbbae41cb + // [4..35] listenerAddr (address, left-padded to 32 bytes) + // [36..67] offset to extraData = 0x40 (64) + // [68..99] extraData length = 0 + const listenerHex = listenerAddr.toHexString().slice(2); // 40 hex chars, no 0x + DataSetCreatedEvent.transaction.input = Bytes.fromHexString( + "0xbbae41cb" + + "000000000000000000000000" + + listenerHex + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000000" + ); + + return DataSetCreatedEvent; +} + +// Mocks the DataSetCreated event triggered by an addPieces() call that creates a new dataset. +// Builds the transaction input for: addPieces(uint256 setId, address listenerAddr, Cids.Cid[], bytes) +// selector 0x9afd37f2 — listenerAddr occupies input[48:68]. +export function createDataSetCreatedFromAddPiecesEvent( + setId: BigInt, + provider: Address, + contractAddress: Address, + listenerAddr: Address, + blockNumber: BigInt = BigInt.fromI32(1), + timestamp: BigInt = BigInt.fromI32(1), + txHash: Bytes = generateTxHash(6), + logIndex: BigInt = BigInt.fromI32(0) +): DataSetCreated { + let DataSetCreatedEvent = changetype(newMockEvent()); + + DataSetCreatedEvent.parameters = new Array(); - // Transaction input is not strictly needed if the handler only uses event.params - // DataSetCreatedEvent.transaction.input = Bytes.fromI32(0); + let setIdParam = new ethereum.EventParam( + "setId", + ethereum.Value.fromUnsignedBigInt(setId) + ); + let providerParam = new ethereum.EventParam( + "storageProvider", + ethereum.Value.fromAddress(provider) + ); + + DataSetCreatedEvent.parameters.push(setIdParam); + DataSetCreatedEvent.parameters.push(providerParam); + + DataSetCreatedEvent.address = contractAddress; + DataSetCreatedEvent.block.number = blockNumber; + DataSetCreatedEvent.block.timestamp = timestamp; + DataSetCreatedEvent.transaction.hash = txHash; + DataSetCreatedEvent.logIndex = logIndex; + DataSetCreatedEvent.transaction.from = provider; + DataSetCreatedEvent.transaction.to = contractAddress; + + // Build addPieces(uint256,address,Cids.Cid[],bytes) calldata: + // [0..3] selector 0x9afd37f2 + // [4..35] setId = 0 (caller passes 0 to signal "create new dataset"; the + // contract assigns the real setId which is reflected in event.params) + // [36..67] listenerAddr (address, left-padded to 32 bytes) + // [68..99] offset to pieceData = 0x80 (128) + // [100..131] offset to extraData = 0xa0 (160) + // [132..163] pieceData length = 0 + // [164..195] extraData length = 0 + const listenerHex = listenerAddr.toHexString().slice(2); // 40 hex chars + DataSetCreatedEvent.transaction.input = Bytes.fromHexString( + "0x9afd37f2" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "000000000000000000000000" + + listenerHex + + "0000000000000000000000000000000000000000000000000000000000000080" + + "00000000000000000000000000000000000000000000000000000000000000a0" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + ); return DataSetCreatedEvent; } diff --git a/subgraph/tests/pdp-verifier.test.ts b/subgraph/tests/pdp-verifier.test.ts index 9f117cf..75bd0b3 100644 --- a/subgraph/tests/pdp-verifier.test.ts +++ b/subgraph/tests/pdp-verifier.test.ts @@ -15,10 +15,18 @@ import { import { createRootsAddedEvent, createDataSetCreatedEvent, + createDataSetCreatedFromAddPiecesEvent, } from "./pdp-verifier-utils"; // Define constants for test data const SET_ID = BigInt.fromI32(1); +const ADD_PIECES_SET_ID = BigInt.fromI32(2); +const ADD_PIECES_LISTENER = Address.fromString( + "0x1111111111111111111111111111111111111111" +); +const ADD_PIECES_TX_HASH = Bytes.fromHexString("0x" + "d".repeat(64)); +const UNKNOWN_SELECTOR_SET_ID = BigInt.fromI32(3); +const UNKNOWN_SELECTOR_TX_HASH = Bytes.fromHexString("0x" + "e".repeat(64)); const ROOT_ID_1 = BigInt.fromI32(101); const RAW_SIZE_1 = BigInt.fromI32(10486897); // CIDs as strings @@ -49,14 +57,16 @@ function stringToBytes32(str: string): Bytes { describe("handlePiecesAdded Tests", () => { beforeAll(() => { - // 1. Create the necessary DataSet first + // 1. Create the necessary DataSet first (via createDataSet call) let mockDataSetCreatedEvent = createDataSetCreatedEvent( SET_ID, SENDER_ADDRESS, - Bytes.fromI32(123), // Dummy root, as it's required by the function but not used by the handler here CONTRACT_ADDRESS, - BigInt.fromI32(50), // Match block number for consistency - BigInt.fromI32(1678886400) // Match timestamp for consistency + BigInt.fromI32(50), + BigInt.fromI32(1678886400), + Bytes.fromHexString("0x" + "a".repeat(64)), + BigInt.fromI32(0), + LISTENER_ADDRESS ); handleDataSetCreated(mockDataSetCreatedEvent); @@ -94,6 +104,12 @@ describe("handlePiecesAdded Tests", () => { // --- Assert DataSet fields --- let dataSetId = PROOF_SET_ID_BYTES.toHex(); assert.fieldEquals("DataSet", dataSetId, "setId", SET_ID.toString()); + assert.fieldEquals( + "DataSet", + dataSetId, + "listener", + LISTENER_ADDRESS.toHexString() + ); assert.fieldEquals("DataSet", dataSetId, "totalRoots", "1"); // Initially 0, added 1 let expectedTotalSize = RAW_SIZE_1.toString(); assert.fieldEquals( @@ -148,3 +164,79 @@ describe("handlePiecesAdded Tests", () => { assert.fieldEquals("EventLog", eventId, "data", expectedData); }); }); + +describe("handleDataSetCreated via addPieces Tests", () => { + beforeAll(() => { + // DataSetCreated emitted from an addPieces() call (setId=0 means new dataset) + let event = createDataSetCreatedFromAddPiecesEvent( + ADD_PIECES_SET_ID, + SENDER_ADDRESS, + CONTRACT_ADDRESS, + ADD_PIECES_LISTENER, + BigInt.fromI32(100), + BigInt.fromI32(2000000), + ADD_PIECES_TX_HASH, + BigInt.fromI32(0) + ); + handleDataSetCreated(event); + }); + + afterAll(() => { + clearStore(); + }); + + test("Listener address decoded correctly from addPieces calldata", () => { + const dataSetId = Bytes.fromBigInt(ADD_PIECES_SET_ID).toHex(); + assert.entityCount("DataSet", 1); + assert.fieldEquals( + "DataSet", + dataSetId, + "setId", + ADD_PIECES_SET_ID.toString() + ); + assert.fieldEquals( + "DataSet", + dataSetId, + "listener", + ADD_PIECES_LISTENER.toHexString() + ); + + // Transaction method should reflect the actual calling function + const txId = ADD_PIECES_TX_HASH.toHex(); + assert.fieldEquals("Transaction", txId, "method", "addPieces"); + }); +}); + +describe("handleDataSetCreated with unknown selector", () => { + beforeAll(() => { + let event = createDataSetCreatedEvent( + UNKNOWN_SELECTOR_SET_ID, + SENDER_ADDRESS, + CONTRACT_ADDRESS, + BigInt.fromI32(300), + BigInt.fromI32(4000000), + UNKNOWN_SELECTOR_TX_HASH, + BigInt.fromI32(0) + ); + // Replace transaction input with an unrecognised selector so the + // warning branch in decodeListenerAddrFromInput is exercised. + event.transaction.input = Bytes.fromHexString("0xdeadbeef"); + handleDataSetCreated(event); + }); + + afterAll(() => { + clearStore(); + }); + + test("DataSet is still created and listener falls back to zero address", () => { + const dataSetId = Bytes.fromBigInt(UNKNOWN_SELECTOR_SET_ID).toHex(); + assert.entityCount("DataSet", 1); + // decodeListenerAddrFromInput returns the zero address for an unknown selector. + assert.fieldEquals( + "DataSet", + dataSetId, + "listener", + "0x0000000000000000000000000000000000000000" + ); + }); +});