Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 81 additions & 20 deletions src/PDPVerifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,23 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable {
require(success, "Burn failed");
}

// Validates msg.value meets sybil fee requirement and burns the fee.
// Returns the sybil fee amount for later refund calculation.
function _validateAndBurnSybilFee() internal returns (uint256 sybilFee) {
sybilFee = PDPFees.sybilFee();
require(msg.value >= sybilFee, "sybil fee not met");
burnFee(sybilFee);
}

// Refunds any amount sent over the sybil fee back to msg.sender.
// Must be called after all state changes to avoid re-entrancy issues.
function _refundExcessSybilFee(uint256 sybilFee) internal {
if (msg.value > sybilFee) {
(bool success,) = msg.sender.call{value: msg.value - sybilFee}("");
require(success, "Transfer failed.");
}
}

// Returns the current challenge finality value
function getChallengeFinality() public view returns (uint256) {
return challengeFinality;
Expand Down Expand Up @@ -387,6 +404,45 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable {
}
}

// Internal helper to create a new data set and initialize its state.
// Returns the newly created data set ID.
function _createDataSet(address listenerAddr, bytes memory extraData) internal returns (uint256) {
uint256 setId = nextDataSetId++;
dataSetLeafCount[setId] = 0;
nextChallengeEpoch[setId] = NO_CHALLENGE_SCHEDULED; // initialized on first call to NextProvingPeriod
storageProvider[setId] = msg.sender;
dataSetListener[setId] = listenerAddr;
dataSetLastProvenEpoch[setId] = NO_PROVEN_EPOCH;

if (listenerAddr != address(0)) {
PDPListener(listenerAddr).dataSetCreated(setId, msg.sender, extraData);
}
emit DataSetCreated(setId, msg.sender);

return setId;
}

// Creates a new empty data set with no pieces. Pieces can be added later via addPieces().
// This is the simpler alternative to creating and adding pieces atomically via addPieces(NEW_DATA_SET_SENTINEL, ...).
//
// Parameters:
// - listenerAddr: Address of PDPListener contract to receive callbacks (can be address(0) for no listener)
// - extraData: Arbitrary bytes passed to listener's dataSetCreated callback
// - msg.value: Must include sybil fee (PDPFees.sybilFee()), excess is refunded
//
// Returns: The newly created data set ID
//
// Only the storage provider (msg.sender) can call this function.
function createDataSet(address listenerAddr, bytes calldata extraData) public payable returns (uint256) {
require(extraData.length <= EXTRA_DATA_MAX_SIZE, "Extra data too large");
uint256 sybilFee = _validateAndBurnSybilFee();

uint256 setId = _createDataSet(listenerAddr, extraData);

_refundExcessSybilFee(sybilFee);
return setId;
}

// Removes a data set. Must be called by the storage provider.
function deleteDataSet(uint256 setId, bytes calldata extraData) public {
require(extraData.length <= EXTRA_DATA_MAX_SIZE, "Extra data too large");
Expand All @@ -408,9 +464,28 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable {
emit DataSetDeleted(setId, deletedLeafCount);
}

// Create Dataset and Add Pieces, When setId == NEW_DATA_SET_SENTINEL, this will create a new dataset with piece data provided
// with the provided listenerAddr and expect extraData to be abi.encode(bytes createPayload, bytes addPayload).
// When adding to an existing set, pass listenerAddr == address(0) and setId to the live dataset.
// Appends pieces to a data set. Optionally creates a new data set if setId == 0.
// These pieces won't be challenged until the next proving period is started by calling nextProvingPeriod.
//
// Two modes of operation:
// 1. Add to existing data set:
// - setId: ID of the existing data set
// - listenerAddr: must be address(0)
// - extraData: arbitrary bytes passed to the listener's piecesAdded callback
// - msg.value: must be 0 (no fee required)
// - Returns: first piece ID added
//
// 2. Create new data set and add pieces (atomic operation):
// - setId: must be NEW_DATA_SET_SENTINEL (0)
// - listenerAddr: listener contract address (required, cannot be address(0))
// - pieceData: array of pieces to add (can be empty to create empty data set)
// - extraData: abi.encode(bytes createPayload, bytes addPayload) where:
// - createPayload: passed to listener's dataSetCreated callback
// - addPayload: passed to listener's piecesAdded callback (if pieces added)
// - msg.value: must include sybil fee (PDPFees.sybilFee()), excess is refunded
// - Returns: the newly created data set ID
//
// Only the storage provider can call this function.
function addPieces(uint256 setId, address listenerAddr, Cids.Cid[] calldata pieceData, bytes calldata extraData)
public
payable
Expand All @@ -420,31 +495,17 @@ contract PDPVerifier is Initializable, UUPSUpgradeable, OwnableUpgradeable {
(bytes memory createPayload, bytes memory addPayload) = abi.decode(extraData, (bytes, bytes));

require(createPayload.length <= EXTRA_DATA_MAX_SIZE, "Extra data too large");
uint256 sybilFee = PDPFees.sybilFee();
require(msg.value >= sybilFee, "sybil fee not met");
burnFee(sybilFee);
uint256 sybilFee = _validateAndBurnSybilFee();

require(listenerAddr != address(0), "listener required for new dataset");
uint256 newSetId = nextDataSetId++;
storageProvider[newSetId] = msg.sender;
dataSetListener[newSetId] = listenerAddr;

if (listenerAddr != address(0)) {
PDPListener(listenerAddr).dataSetCreated(newSetId, msg.sender, createPayload);
}
emit DataSetCreated(newSetId, msg.sender);
uint256 newSetId = _createDataSet(listenerAddr, createPayload);

// Add pieces to the newly created data set (if any)
if (pieceData.length > 0) {
_addPiecesToDataSet(newSetId, pieceData, addPayload);
}

// Return the at the end to avoid any possible re-entrency issues.
if (msg.value > sybilFee) {
(bool success,) = msg.sender.call{value: msg.value - sybilFee}("");
require(success, "Transfer failed.");
}

_refundExcessSybilFee(sybilFee);
return newSetId;
} else {
// Adding to an existing set; no fee should be sent and listenerAddr must be zero
Expand Down
40 changes: 10 additions & 30 deletions test/PDPVerifier.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,7 @@ contract PDPVerifierDataSetCreateDeleteTest is MockFVMTest, PieceHelper {
vm.expectEmit(true, true, false, false);
emit IPDPEvents.DataSetCreated(1, address(this));

uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}(
NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty)
);
uint256 setId = pdpVerifier.createDataSet{value: PDPFees.sybilFee()}(address(listener), empty);
assertEq(setId, 1, "First data set ID should be 1");
assertEq(pdpVerifier.getDataSetLeafCount(setId), 0, "Data set leaf count should be 0");

Expand All @@ -66,9 +64,7 @@ contract PDPVerifierDataSetCreateDeleteTest is MockFVMTest, PieceHelper {
function testDeleteDataSet() public {
vm.expectEmit(true, true, false, false);
emit IPDPEvents.DataSetCreated(1, address(this));
uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}(
NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty)
);
uint256 setId = pdpVerifier.createDataSet{value: PDPFees.sybilFee()}(address(listener), empty);
vm.expectEmit(true, true, false, false);
emit IPDPEvents.DataSetDeleted(setId, 0);
pdpVerifier.deleteDataSet(setId, empty);
Expand All @@ -79,9 +75,7 @@ contract PDPVerifierDataSetCreateDeleteTest is MockFVMTest, PieceHelper {
function testOnlyStorageProviderCanDeleteDataSet() public {
vm.expectEmit(true, true, false, false);
emit IPDPEvents.DataSetCreated(1, address(this));
uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}(
NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty)
);
uint256 setId = pdpVerifier.createDataSet{value: PDPFees.sybilFee()}(address(listener), empty);
// Create a new address to act as a non-storage-provider
address nonStorageProvider = address(0x1234);
// Expect revert when non-storage-provider tries to delete the data set
Expand Down Expand Up @@ -111,9 +105,7 @@ contract PDPVerifierDataSetCreateDeleteTest is MockFVMTest, PieceHelper {
function testMethodsOnDeletedDataSetFails() public {
vm.expectEmit(true, true, false, false);
emit IPDPEvents.DataSetCreated(1, address(this));
uint256 setId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}(
NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty)
);
uint256 setId = pdpVerifier.createDataSet{value: PDPFees.sybilFee()}(address(listener), empty);

vm.expectEmit(true, true, false, false);
emit IPDPEvents.DataSetDeleted(setId, 0);
Expand All @@ -139,14 +131,10 @@ contract PDPVerifierDataSetCreateDeleteTest is MockFVMTest, PieceHelper {
function testGetDataSetID() public {
vm.expectEmit(true, true, false, false);
emit IPDPEvents.DataSetCreated(1, address(this));
pdpVerifier.addPieces{value: PDPFees.sybilFee()}(
NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty)
);
pdpVerifier.createDataSet{value: PDPFees.sybilFee()}(address(listener), empty);
vm.expectEmit(true, true, false, false);
emit IPDPEvents.DataSetCreated(2, address(this));
pdpVerifier.addPieces{value: PDPFees.sybilFee()}(
NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty)
);
pdpVerifier.createDataSet{value: PDPFees.sybilFee()}(address(listener), empty);
assertEq(3, pdpVerifier.getNextDataSetId(), "Next data set ID should be 3");
assertEq(3, pdpVerifier.getNextDataSetId(), "Next data set ID should be 3");
}
Expand All @@ -157,14 +145,10 @@ contract PDPVerifierDataSetCreateDeleteTest is MockFVMTest, PieceHelper {
// Test that data set IDs start from 1, not 0
assertEq(pdpVerifier.getNextDataSetId(), 1, "Next data set ID should start at 1");

uint256 firstSetId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}(
NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty)
);
uint256 firstSetId = pdpVerifier.createDataSet{value: PDPFees.sybilFee()}(address(listener), empty);
assertEq(firstSetId, 1, "First data set ID should be 1, not 0");

uint256 secondSetId = pdpVerifier.addPieces{value: PDPFees.sybilFee()}(
NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty)
);
uint256 secondSetId = pdpVerifier.createDataSet{value: PDPFees.sybilFee()}(address(listener), empty);
assertEq(secondSetId, 2, "Second data set ID should be 2");

assertEq(pdpVerifier.getNextDataSetId(), 3, "Next data set ID should be 3 after creating two data sets");
Expand All @@ -175,17 +159,13 @@ contract PDPVerifierDataSetCreateDeleteTest is MockFVMTest, PieceHelper {

// Test 1: Fails when sending not enough for sybil fee
vm.expectRevert("sybil fee not met");
pdpVerifier.addPieces{value: sybilFee - 1}(
NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty)
);
pdpVerifier.createDataSet{value: sybilFee - 1}(address(listener), empty);

// Test 2: Returns funds over the sybil fee back to the sender
uint256 excessAmount = 1 ether;
uint256 initialBalance = address(this).balance;

uint256 setId = pdpVerifier.addPieces{value: sybilFee + excessAmount}(
NEW_DATA_SET_SENTINEL, address(listener), new Cids.Cid[](0), abi.encode(empty, empty)
);
uint256 setId = pdpVerifier.createDataSet{value: sybilFee + excessAmount}(address(listener), empty);

uint256 finalBalance = address(this).balance;
uint256 refundedAmount = finalBalance - (initialBalance - sybilFee - excessAmount);
Expand Down