-
Notifications
You must be signed in to change notification settings - Fork 25
PoRep Service #459
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
wjmelements
wants to merge
44
commits into
main
Choose a base branch
from
porep
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
PoRep Service #459
Changes from all commits
Commits
Show all changes
44 commits
Select commit
Hold shift + click to select a range
cdfc712
WIP porep
wjmelements 87667b8
PoRepDeal
wjmelements 6a31a9c
getActorId
wjmelements cd9af78
allow multiple deals per sector
wjmelements 571af49
authenticate with nonce and move payments operator back to service
wjmelements 6440ca3
expiry and lockup
wjmelements 850489d
fix overlock with one time payments
wjmelements c6ba636
handle amortization after endEpoch
wjmelements d2bbf01
onBadSector
wjmelements 7333617
bounty insurance via service fee
wjmelements 7c17b8a
termination
wjmelements 27d930d
move IValidator back to Service and share termination path
wjmelements 5b1f1af
test payments and bounties
wjmelements dde7e37
test(PoRepDealSectorStatus): assert rail finalized after termination
wjmelements 1831e48
test(PoRepDealExtend): add test coverage for extend
wjmelements c1f34fc
use LibClone for CREATE2 calculation
wjmelements 0fe7918
rm extra cast
wjmelements d4ba9ef
test(PoRepService): add test coverage for NotMiner error
wjmelements 10d7078
NotMiner error
wjmelements b2c26d9
document that the minerActor is validated in the deal
wjmelements c897e58
CommP CIDv1 digest is 32 bytes
wjmelements f323c06
pull fvm-solidity fixes
wjmelements 4c32e31
fix: update fws-payments submodule URL to filecoin-pay
wjmelements b5d7593
feat(porep): define custom errors and add test coverage
wjmelements 516c13a
feat(porep): emit DealCreated event and verify in tests
wjmelements e75d634
test(porep): add authenticateDeal unauthorized coverage for payee, re…
wjmelements 40a3ead
rm duplicated import
wjmelements b2d00aa
fix(PoRepDeal): pass sector location to sectorExpired for Dead valida…
wjmelements a1dca34
test: SectorNotDead
wjmelements 101ca0a
.gas-snapshot
wjmelements f4b767f
avoid recalculating slot for sectors[sectorId]
wjmelements 37ef865
test(PoRepDeal): expand revert coverage and remove dead SectorNotInDe…
wjmelements a9dfa5d
test(PoRepPayee): assert sudo forwards inner call revert data
wjmelements 1f4dc90
update .gas-snapshot
wjmelements 166bd89
drop trailing cbor metadata from contract bytecode
wjmelements b465f34
ci(gas): fetch full depth to resolve nested submodule commits
wjmelements 6e4f997
Revert "ci(gas): fetch full depth to resolve nested submodule commits"
wjmelements efc9c99
dump submodule info
wjmelements 173af12
test: forge snapshot --isolate
wjmelements 6155d6e
workaround with no-match
wjmelements e3ceb73
dep: point lib/fvm-solidity to main after merge
wjmelements e7c9358
Merge remote-tracking branch 'origin/main' into porep
wjmelements 58c3ab5
chore: update .gas-snapshot
wjmelements 345ba41
chore: bump gas CI forge from 1.3.5 to 1.7.1
wjmelements File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| name: Gas Snapshot | ||
|
|
||
| on: | ||
| push: | ||
| branches: ["main"] | ||
| pull_request: | ||
| branches: ["main"] | ||
|
|
||
| jobs: | ||
| gas-snapshot: | ||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| submodules: recursive | ||
|
|
||
| - name: Install Foundry | ||
| uses: foundry-rs/foundry-toolchain@v1 | ||
| with: | ||
| version: v1.7.1 | ||
|
|
||
| - name: Delete gas snapshot | ||
| run: rm -f service_contracts/.gas-snapshot | ||
|
|
||
| - name: Regenerate gas snapshot | ||
| run: | | ||
| export PATH="/home/runner/.config/.foundry/bin:$PATH" | ||
| make -C service_contracts test | ||
|
|
||
| - name: Dump submodules | ||
| run: | | ||
| git submodule foreach --recursive ' | ||
| depth=$(echo "$displaypath" | tr -cd "/" | wc -c) | ||
| indent=$(printf " %.0s" $(seq 1 $depth)) | ||
| printf "%s%s → %s\n" "$indent" "$displaypath" "$sha1" | ||
| ' | ||
|
|
||
| - name: Check for uncommitted changes | ||
| run: | | ||
| git add service_contracts/.gas-snapshot | ||
| git diff --cached --exit-code service_contracts/.gas-snapshot || { | ||
| echo "Gas snapshot is out of date. Run 'make test' locally and commit the updated .gas-snapshot file." | ||
| exit 1 | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Submodule fvm-solidity
added at
2b3412
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,250 @@ | ||
| // SPDX-License-Identifier: Apache-2.0 OR MIT | ||
| pragma solidity ^0.8.30; | ||
|
|
||
| import {FilecoinPayV1} from "@fws-payments/FilecoinPayV1.sol"; | ||
| import {FVMSector, SectorStatus} from "@fvm-solidity/FVMSector.sol"; | ||
| import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
|
|
||
| interface IPoRepService { | ||
| function updateLockups(uint64 nonce, uint256 railId, uint256 payment, uint256 remaining) external; | ||
| function terminate(uint64 nonce, uint256 railId, uint64 provider, address sender) external; | ||
| } | ||
|
|
||
| contract PoRepDeal { | ||
| address public immutable SERVICE; | ||
| address public immutable CLIENT; | ||
| uint64 public immutable PROVIDER; | ||
| FilecoinPayV1 private immutable PAYMENTS; | ||
| uint256 public immutable RAIL_ID; | ||
| IERC20 public immutable TOKEN; | ||
| uint256 public immutable TOKENS_PER_BYTE_PER_EPOCH; | ||
| uint64 private immutable NONCE; | ||
|
|
||
| struct Info { | ||
| uint64 settledEpoch; | ||
| uint64 endEpoch; | ||
| uint32 faultedSectorCount; | ||
| uint96 totalActiveSize; | ||
| } | ||
|
|
||
| Info public info; | ||
|
|
||
| enum PieceStatus { | ||
| UNAUTHORIZED, | ||
| AUTHORIZED, | ||
| ACTIVE | ||
| } | ||
|
|
||
| // TODO move to child contract | ||
| mapping(bytes32 pieceId => PieceStatus) pieces; | ||
|
|
||
| // TODO move to child contract | ||
| struct SectorInfo { | ||
| uint96 dealSize; | ||
| uint8 failed; | ||
| } | ||
|
|
||
| mapping(uint256 sectorId => SectorInfo) public sectors; | ||
|
|
||
| error Unauthorized(); | ||
| error PieceAlreadyAuthorized(bytes32 pieceDigest); | ||
| error WrongProvider(uint64 minerId); | ||
| error CommitmentTooShort(uint64 minimumCommitmentEpoch, uint64 endEpoch); | ||
| error PieceNotAuthorized(bytes32 pieceDigest); | ||
| error DealExpired(); | ||
| error DealFaulted(); | ||
| error DealNotExpired(); | ||
| error SectorNotInDeal(uint64 sectorId); | ||
| error SectorAlreadyFailed(uint64 sectorId); | ||
| error SectorNotFailed(uint64 sectorId); | ||
| error SectorNotFaulty(uint64 sectorId); | ||
| error SectorNotActive(uint64 sectorId); | ||
| error SectorNotDead(uint64 sectorId); | ||
|
|
||
| constructor( | ||
| address service, | ||
| address client, | ||
| uint64 provider, | ||
| FilecoinPayV1 payments, | ||
| uint256 railId, | ||
| IERC20 token, | ||
| uint256 tokensPerBytePerEpoch, | ||
| uint64 dealEndEpoch, | ||
| uint64 nonce | ||
| ) { | ||
| SERVICE = service; | ||
| CLIENT = client; | ||
| PROVIDER = provider; | ||
| PAYMENTS = payments; | ||
| TOKEN = token; | ||
| TOKENS_PER_BYTE_PER_EPOCH = tokensPerBytePerEpoch; | ||
| RAIL_ID = railId; | ||
| NONCE = nonce; | ||
| info.endEpoch = dealEndEpoch; | ||
| } | ||
|
|
||
| function _onlyClient() internal view { | ||
| require(msg.sender == CLIENT, Unauthorized()); | ||
| } | ||
|
|
||
| function _onlyService() internal view { | ||
| require(msg.sender == SERVICE, Unauthorized()); | ||
| } | ||
|
|
||
| modifier onlyClient() { | ||
| _onlyClient(); | ||
| _; | ||
| } | ||
|
|
||
| modifier onlyService() { | ||
| _onlyService(); | ||
| _; | ||
| } | ||
|
|
||
| // TODO allow provider to addPieces with client authorization | ||
| function addPieces(bytes32[] calldata pieceDigests) external onlyClient { | ||
| for (uint256 i = 0; i < pieceDigests.length; i++) { | ||
| bytes32 pieceDigest = pieceDigests[i]; | ||
| require(pieces[pieceDigest] == PieceStatus.UNAUTHORIZED, PieceAlreadyAuthorized(pieceDigest)); | ||
| pieces[pieceDigest] = PieceStatus.AUTHORIZED; | ||
| } | ||
| } | ||
|
|
||
| function pieceAdded( | ||
| uint64 minerId, | ||
| bytes32 pieceDigest, | ||
| uint64 sectorId, | ||
| uint64 minimumCommitmentEpoch, | ||
| uint64 paddedSize | ||
| ) external onlyService { | ||
| require(minerId == PROVIDER, WrongProvider(minerId)); | ||
|
|
||
| // this also enforces block.number < info.endEpoch because minimum commitment is 180 days | ||
| require(minimumCommitmentEpoch >= info.endEpoch, CommitmentTooShort(minimumCommitmentEpoch, info.endEpoch)); | ||
|
|
||
| require(pieces[pieceDigest] == PieceStatus.AUTHORIZED, PieceNotAuthorized(pieceDigest)); | ||
| pieces[pieceDigest] = PieceStatus.ACTIVE; | ||
|
|
||
| sectors[sectorId].dealSize += paddedSize; | ||
|
|
||
| // TODO only amortize once per SectorContentChanged notification | ||
| uint256 prevSize = info.totalActiveSize; | ||
| uint256 newSize = prevSize + paddedSize; | ||
| uint256 prevRate = info.faultedSectorCount > 0 ? 0 : prevSize * TOKENS_PER_BYTE_PER_EPOCH; | ||
| uint256 newRate = newSize * TOKENS_PER_BYTE_PER_EPOCH; | ||
| amortize((block.number - info.settledEpoch) * prevRate, (info.endEpoch - block.number) * newRate); | ||
| info.settledEpoch = uint64(block.number); | ||
| info.totalActiveSize = uint96(newSize); | ||
| } | ||
|
|
||
| function extend(uint64 epochs) external onlyClient { | ||
| require(block.number < info.endEpoch, DealExpired()); | ||
| require(info.faultedSectorCount == 0, DealFaulted()); | ||
| uint64 newEndEpoch = info.endEpoch + epochs; | ||
| uint256 rate = info.totalActiveSize * TOKENS_PER_BYTE_PER_EPOCH; | ||
| amortize((block.number - info.settledEpoch) * rate, (newEndEpoch - block.number) * rate); | ||
| info.endEpoch = newEndEpoch; | ||
| info.settledEpoch = uint64(block.number); | ||
| } | ||
|
|
||
| function amortize(uint256 payment, uint256 remaining) internal { | ||
| IPoRepService(SERVICE).updateLockups(NONCE, RAIL_ID, payment, remaining); | ||
| } | ||
|
|
||
| function amortize() public { | ||
| if (info.faultedSectorCount == 0) { | ||
| amortizeHealthy(); | ||
| return; | ||
| } | ||
| info.settledEpoch = uint64(block.number); | ||
| } | ||
|
|
||
| function amortizeHealthy() internal { | ||
| uint256 rate = info.totalActiveSize * TOKENS_PER_BYTE_PER_EPOCH; | ||
| if (block.number < info.endEpoch) { | ||
| // live | ||
| amortize((block.number - info.settledEpoch) * rate, (info.endEpoch - block.number) * rate); | ||
| } else { | ||
| // expired | ||
| amortize((info.endEpoch - info.settledEpoch) * rate, 0); | ||
| } | ||
| info.settledEpoch = uint64(block.number); | ||
| } | ||
|
|
||
| function getInsuranceFunds() internal view returns (uint256 funds) { | ||
| (funds,,,) = PAYMENTS.accounts(TOKEN, address(this)); | ||
| } | ||
|
|
||
| function payoutBounty(address recipient, uint256 bounty) internal { | ||
| if (bounty > 0) { | ||
| PAYMENTS.withdrawTo(TOKEN, recipient, bounty); | ||
| } | ||
| } | ||
|
|
||
| // Pass NO_DEADLINE and NO_PARTITION once the sector has been compacted via CompactPartitions. | ||
| function sectorExpired(uint64 sectorId, int64 deadline, int64 partition, address recipient) external { | ||
| require(block.number < info.endEpoch, DealExpired()); | ||
| require(sectors[sectorId].dealSize > 0, SectorNotInDeal(sectorId)); | ||
| require( | ||
| FVMSector.validateSectorStatus(PROVIDER, sectorId, SectorStatus.Dead, deadline, partition), | ||
| SectorNotDead(sectorId) | ||
| ); | ||
|
|
||
| // this is unrecoverable, so terminate | ||
| info.endEpoch = uint64(block.number); | ||
| amortize(); | ||
| terminate(recipient, 0, address(0)); | ||
| } | ||
|
|
||
| function sectorFaulty(uint64 sectorId, int64 deadline, int64 partition, address recipient) external { | ||
| require(block.number < info.endEpoch, DealExpired()); | ||
|
|
||
| SectorInfo storage sectorInfo = sectors[sectorId]; | ||
| require(sectorInfo.dealSize > 0, SectorNotInDeal(sectorId)); | ||
| require(sectorInfo.failed == 0, SectorAlreadyFailed(sectorId)); | ||
| require( | ||
| FVMSector.validateSectorStatus(PROVIDER, sectorId, SectorStatus.Faulty, deadline, partition), | ||
| SectorNotFaulty(sectorId) | ||
| ); | ||
|
|
||
| sectorInfo.failed = 1; | ||
| if (info.faultedSectorCount == 0) { | ||
| amortizeHealthy(); | ||
| info.faultedSectorCount = 1; | ||
| payoutBounty(recipient, getInsuranceFunds() / 2); | ||
| } else { | ||
| info.faultedSectorCount++; | ||
| } | ||
| } | ||
|
|
||
| // SPs should call this after DeclareFaultsRecovered and a successful Window PoSt | ||
| function sectorRecovered(uint64 sectorId, int64 deadline, int64 partition) external { | ||
| SectorInfo storage sectorInfo = sectors[sectorId]; | ||
| // a sector cannot have been marked faulty unless it is already in the deal | ||
| require(sectorInfo.failed == 1, SectorNotFailed(sectorId)); | ||
| require( | ||
| FVMSector.validateSectorStatus(PROVIDER, sectorId, SectorStatus.Active, deadline, partition), | ||
| SectorNotActive(sectorId) | ||
| ); | ||
|
|
||
| sectorInfo.failed = 0; | ||
|
|
||
| if (--info.faultedSectorCount == 0) { | ||
| info.settledEpoch = uint64(block.number); | ||
| } | ||
| } | ||
|
|
||
| function terminate(address recipient, uint64 provider, address receiver) internal { | ||
| // if termination is caused by a dead sector, the keeper gets the insurance | ||
| // otherwise, the insurance is paid to the order of the PROVIDER | ||
| IPoRepService(SERVICE).terminate(NONCE, RAIL_ID, provider, receiver); | ||
| payoutBounty(recipient, getInsuranceFunds()); | ||
| } | ||
|
|
||
| // After healthy deal termination, the remainder of the insurance funds can be collected by the receiver in exchange for rail cleanup | ||
| function sweep(address recipient) external { | ||
| require(block.number > info.endEpoch, DealNotExpired()); | ||
| amortize(); | ||
| terminate(recipient, PROVIDER, msg.sender); | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm inclined to remove this file. I introduced it mainly to check for solc bytecode improvements and regressions. It has been a pain to get it to be deterministic and I suspect it will cause other people trouble in the future, and more trouble than it is worth.