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
72 changes: 72 additions & 0 deletions abis/PoRepMarket.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,78 @@
"outputs": [],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "getCompletedDeals",
"inputs": [],
"outputs": [
{
"name": "completedDeals",
"type": "tuple[]",
"internalType": "struct PoRepMarket.DealProposal[]",
"components": [
{
"name": "dealId",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "client",
"type": "address",
"internalType": "address"
},
{
"name": "provider",
"type": "uint64",
"internalType": "CommonTypes.FilActorId"
},
{
"name": "requirements",
"type": "tuple",
"internalType": "struct SLITypes.SLIThresholds",
"components": [
{
"name": "retrievabilityPct",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "bandwidthMbps",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "latencyMs",
"type": "uint16",
"internalType": "uint16"
},
{
"name": "indexingPct",
"type": "uint8",
"internalType": "uint8"
}
]
},
{
"name": "validator",
"type": "address",
"internalType": "address"
},
{
"name": "state",
"type": "uint8",
"internalType": "enum PoRepMarket.DealState"
},
{
"name": "railId",
"type": "uint256",
"internalType": "uint256"
}
]
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "getDealProposal",
Expand Down
1 change: 0 additions & 1 deletion abis/SLITypes.json

This file was deleted.

2 changes: 1 addition & 1 deletion gen-abis.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ set -euo pipefail
rm -rf abis
mkdir abis

for i in $(find src -name '*.sol' ! -path "*/interfaces/*"); do
for i in $(find src -name '*.sol' ! -path "*/interfaces/*" ! -path "*/types/*"); do
echo "Generating ABI file for $i"
i="$(basename "$i")"
name="${i%.sol}"
Expand Down
53 changes: 46 additions & 7 deletions src/PoRepMarket.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ import {ISPRegistry} from "./interfaces/ISPRegistry.sol";
import {ValidatorFactory} from "./ValidatorFactory.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {SLITypes} from "./types/SLITypes.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

/**
* @title PoRepMarket contract
* @dev PoRepMarket contract is a contract that allows users to create and manage deal proposals for PoRep deals
* @notice PoRepMarket contract
*/
contract PoRepMarket is Initializable, AccessControlUpgradeable, UUPSUpgradeable {
using EnumerableSet for EnumerableSet.UintSet;
/**
* @notice role to manage contract upgrades
*/
Expand All @@ -25,6 +27,7 @@ contract PoRepMarket is Initializable, AccessControlUpgradeable, UUPSUpgradeable
/// @custom:storage-location erc7201:porepmarket.storage.DealProposalsStorage
struct DealProposalsStorage {
mapping(uint256 dealId => DealProposal) _dealProposals;
EnumerableSet.UintSet _dealIdsReadyForPayment;
ISPRegistry _SPRegistryContract;
ValidatorFactory _validatorFactoryContract;
address _clientSmartContract;
Expand All @@ -42,6 +45,15 @@ contract PoRepMarket is Initializable, AccessControlUpgradeable, UUPSUpgradeable
}
}

/**
* @dev Returns the storage struct for the PoRepMarket contract.
* @notice function to allow acess to storage
* @return DealProposalsStorage storage struct
*/
function s() private pure returns (DealProposalsStorage storage) {
return _getDealProposalsStorage();
}

/**
* @notice DealState enum
* @dev DealState enum is an enum that contains the states of a deal
Expand Down Expand Up @@ -152,7 +164,7 @@ contract PoRepMarket is Initializable, AccessControlUpgradeable, UUPSUpgradeable
_grantRole(DEFAULT_ADMIN_ROLE, _admin);
_grantRole(UPGRADER_ROLE, _admin);

DealProposalsStorage storage $ = _getDealProposalsStorage();
DealProposalsStorage storage $ = s();
$._validatorFactoryContract = ValidatorFactory(_validatorFactory);
$._SPRegistryContract = ISPRegistry(_spRegistry);
$._clientSmartContract = _clientSmartContract;
Expand All @@ -172,7 +184,7 @@ contract PoRepMarket is Initializable, AccessControlUpgradeable, UUPSUpgradeable
revert InvalidIndexingPct(requirements.indexingPct);
}

DealProposalsStorage storage $ = _getDealProposalsStorage();
DealProposalsStorage storage $ = s();

CommonTypes.FilActorId provider = $._SPRegistryContract.getProviderForDeal(requirements, terms);
if (CommonTypes.FilActorId.unwrap(provider) == 0) {
Expand Down Expand Up @@ -202,7 +214,7 @@ contract PoRepMarket is Initializable, AccessControlUpgradeable, UUPSUpgradeable
* @param railId The id of the rail
*/
function updateValidatorAndRailId(uint256 dealId, uint256 railId) external {
DealProposalsStorage storage $ = _getDealProposalsStorage();
DealProposalsStorage storage $ = s();
DealProposal storage dp = $._dealProposals[dealId];

_ensureDealExists(dp);
Expand All @@ -228,7 +240,7 @@ contract PoRepMarket is Initializable, AccessControlUpgradeable, UUPSUpgradeable
* @return DealProposal The deal proposal
*/
function getDealProposal(uint256 dealId) external view returns (DealProposal memory) {
DealProposalsStorage storage $ = _getDealProposalsStorage();
DealProposalsStorage storage $ = s();
return $._dealProposals[dealId];
}

Expand All @@ -238,7 +250,7 @@ contract PoRepMarket is Initializable, AccessControlUpgradeable, UUPSUpgradeable
* @param dealId The id of the deal proposal
*/
function acceptDeal(uint256 dealId) external {
DealProposalsStorage storage $ = _getDealProposalsStorage();
DealProposalsStorage storage $ = s();
DealProposal storage dp = $._dealProposals[dealId];

_ensureDealExists(dp);
Expand All @@ -258,7 +270,7 @@ contract PoRepMarket is Initializable, AccessControlUpgradeable, UUPSUpgradeable
* @param dealId The id of the deal proposal
*/
function completeDeal(uint256 dealId) external {
DealProposalsStorage storage $ = _getDealProposalsStorage();
DealProposalsStorage storage $ = s();
DealProposal storage dp = $._dealProposals[dealId];

_ensureDealExists(dp);
Expand All @@ -267,9 +279,11 @@ contract PoRepMarket is Initializable, AccessControlUpgradeable, UUPSUpgradeable
if (msg.sender != $._clientSmartContract) revert NotTheClientSmartContract(dealId, msg.sender);

dp.state = DealState.Completed;
$._dealIdsReadyForPayment.add(dealId);
// TODO: Call $._SPRegistryContract.commitCapacity(dp.provider, actualSizeBytes)
// when completeDeal signature is updated with actualSizeBytes from Client contract.
// REF: Client.sol (PR #4)

emit DealCompleted(dealId, msg.sender, dp.provider);
}

Expand All @@ -279,7 +293,7 @@ contract PoRepMarket is Initializable, AccessControlUpgradeable, UUPSUpgradeable
* @param dealId The id of the deal proposal
*/
function rejectDeal(uint256 dealId) external {
DealProposalsStorage storage $ = _getDealProposalsStorage();
DealProposalsStorage storage $ = s();
DealProposal storage dp = $._dealProposals[dealId];

_ensureDealExists(dp);
Expand All @@ -293,6 +307,31 @@ contract PoRepMarket is Initializable, AccessControlUpgradeable, UUPSUpgradeable
emit DealRejected(dealId, msg.sender);
}

/**
* @notice Gets all completed deals
* @dev Iterates through all deals and returns only those with Completed state
* @return completedDeals Array of completed deal proposals
*/
function getCompletedDeals() external view returns (DealProposal[] memory completedDeals) {
DealProposalsStorage storage $ = s();
uint256[] memory completedDealsIds = $._dealIdsReadyForPayment.values();
completedDeals = new DealProposal[](completedDealsIds.length);
uint256 dealCounter = 0;

for (uint256 i = 0; i < completedDealsIds.length; i++) {
DealProposal memory dp = $._dealProposals[completedDealsIds[i]];
if (dp.state == DealState.Completed) {
completedDeals[dealCounter] = dp;
dealCounter++;
}
}

// solhint-disable-next-line no-inline-assembly
assembly ("memory-safe") {
mstore(completedDeals, dealCounter)
}
}

/**
* @notice Ensures a deal exists
* @dev Ensures a deal exists by checking if the deal id exists
Expand Down
64 changes: 64 additions & 0 deletions test/PoRepMarket.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.s
import {CommonTypes} from "filecoin-solidity/v0.8/types/CommonTypes.sol";
import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol";
import {SLITypes} from "../src/types/SLITypes.sol";
import {PoRepMarketContractMock} from "./contracts/PoRepMarketContractMock.sol";

// solhint-disable-next-line max-states-count
contract PoRepMarketTest is Test {
Expand Down Expand Up @@ -62,6 +63,22 @@ contract PoRepMarketTest is Test {
validatorFactory.setValidator(validatorAddress, true);
}

function createDealProposal(uint256 proposalDealId, PoRepMarket.DealState state)
public
view
returns (PoRepMarket.DealProposal memory)
{
return PoRepMarket.DealProposal({
dealId: proposalDealId,
client: clientAddress,
provider: providerFilActorId,
requirements: defaultRequirements,
validator: validatorAddress,
railId: railId,
state: state
});
}

function testProposeDealEmitsEvent() public {
vm.prank(clientAddress);
vm.expectEmit(true, true, true, true);
Expand Down Expand Up @@ -232,6 +249,30 @@ contract PoRepMarketTest is Test {
poRepMarket.completeDeal(dealId);
}

function testShouldAddDealIdToCompletedDealsIdsSet() public {
PoRepMarketContractMock impl = new PoRepMarketContractMock();
bytes memory initData = abi.encodeWithSignature(
"initialize(address,address,address,address)",
adminAddress,
address(validatorFactory),
address(spRegistry),
clientSmartContractAddress
);
ERC1967Proxy proxy = new ERC1967Proxy(address(impl), initData);
PoRepMarketContractMock porepMarekMock = PoRepMarketContractMock(address(proxy));
vm.prank(clientAddress);
porepMarekMock.proposeDeal(defaultRequirements, defaultTerms);
vm.prank(providerOwnerAddress);
porepMarekMock.acceptDeal(dealId);

vm.prank(clientSmartContractAddress);
porepMarekMock.completeDeal(dealId);

uint256[] memory completedDealsIds = porepMarekMock.getCompletedDealsIds();
assertEq(completedDealsIds.length, 1);
assertEq(completedDealsIds[0], dealId);
}

function testCompleteDealRevertsWhenDealDoesNotExist() public {
vm.expectRevert(abi.encodeWithSelector(PoRepMarket.DealDoesNotExist.selector));
poRepMarket.completeDeal(dealId);
Expand Down Expand Up @@ -352,4 +393,27 @@ contract PoRepMarketTest is Test {
vm.expectRevert(abi.encodeWithSelector(PoRepMarket.InvalidIndexingPct.selector, uint8(101)));
poRepMarket.proposeDeal(badRequirements, defaultTerms);
}

function testGetCompletedDeals() public {
PoRepMarketContractMock porepMarekMock = new PoRepMarketContractMock();
uint256[] memory ids = new uint256[](5);
ids[0] = 1;
ids[1] = 2;
ids[2] = 3;
ids[3] = 4;
ids[4] = 5;
porepMarekMock.setDealProposal(createDealProposal(ids[0], PoRepMarket.DealState.Completed));
porepMarekMock.setDealProposal(createDealProposal(ids[1], PoRepMarket.DealState.Accepted));
porepMarekMock.setDealProposal(createDealProposal(ids[2], PoRepMarket.DealState.Proposed));
porepMarekMock.setDealProposal(createDealProposal(ids[3], PoRepMarket.DealState.Completed));
porepMarekMock.setDealProposal(createDealProposal(ids[4], PoRepMarket.DealState.Rejected));
porepMarekMock.setDealIdsReadyForPayment(ids);

PoRepMarket.DealProposal[] memory dealProposal = porepMarekMock.getCompletedDeals();
assertEq(dealProposal.length, 2);
assertEq(dealProposal[0].dealId, ids[0]);
assertTrue(dealProposal[0].state == PoRepMarket.DealState.Completed);
assertEq(dealProposal[1].dealId, ids[3]);
assertTrue(dealProposal[1].state == PoRepMarket.DealState.Completed);
}
}
33 changes: 33 additions & 0 deletions test/contracts/PoRepMarketContractMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// solhint-disable use-natspec
// SPDX-License-Identifier: MIT

pragma solidity 0.8.25;

import {PoRepMarket} from "../../src/PoRepMarket.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

contract PoRepMarketContractMock is PoRepMarket {
using EnumerableSet for EnumerableSet.UintSet;

function _getStorage() private pure returns (PoRepMarket.DealProposalsStorage storage $) {
// solhint-disable-next-line no-inline-assembly
assembly ("memory-safe") {
$.slot := 0xea093611145db18b250f1cd58e07fc50de512902beb662a10f8e6d1dd55f6700
}
}

function setDealProposal(PoRepMarket.DealProposal calldata dealProposal) external {
_getStorage()._dealProposals[++_getStorage()._dealIdCounter] = dealProposal;
}

function setDealIdsReadyForPayment(uint256[] calldata dealIds) external {
for (uint256 i = 0; i < dealIds.length; i++) {
_getStorage()._dealIdsReadyForPayment.add(dealIds[i]);
}
}

function getCompletedDealsIds() public view returns (uint256[] memory) {
return _getStorage()._dealIdsReadyForPayment.values();
}
}