Skip to content

Commit 43461e0

Browse files
authored
Merge pull request #18 from SiegfriedBz/feature/fix-publisher-reviewer-pool-sync
fix(contracts): sync publisher into reviewer pool after publication is published ## Summary This PR fixes a state synchronization issue where publishers were not being added to the available reviewer pool after their locked stake was returned upon publication settlement. It ensures that the protocol correctly evaluates a publisher's eligibility for the reviewer pool based on their updated `availableStake`. ## Problem In the `_settlePublication` logic, the contract successfully returned the locked stake to the publisher when a publication reached the `PUBLISHED` state. However, it was not triggering `_syncReviewerStatus` for the publisher address. Consequently, even if the returned stake pushed the publisher’s balance above the required threshold to act as a reviewer, their status in the `availableReviewersPool` remained stale, preventing them from being selected for future reviews until a manual or unrelated state change occurred. ## Solution - Modified `_settlePublication` to include a call to `_syncReviewerStatus(pub.publisher)` after the stake is unlocked and returned. - This ensures the `availableReviewersPool` and `isAvailableReviewer` mapping are updated immediately to reflect the publisher's new liquid stake. ## Changes ### Smart Contract - Added `_syncReviewerStatus(pub.publisher)` within the settlement branch for successful publications. ### Testing & Verification - **Updated Existing Tests**: Enhanced current publication tests to include assertions for the `IsAvailableReviewer` event. - **New Test Case**: `test_PublishPublication_PublisherBecomesAvailableReviewer` - Validates that a publisher with zero initial available stake is correctly added to the reviewer pool once their publication stake is returned. - Asserted all three parameters of the `IsAvailableReviewer` event to ensure data integrity.
2 parents 9f6d0c7 + fab5852 commit 43461e0

4 files changed

Lines changed: 59 additions & 9 deletions

File tree

apps/contracts/src/BioVerifyV3.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,7 @@ contract BioVerifyV3 is VRFConsumerBaseV2Plus, ReentrancyGuard {
523523
if (_newStatus == PublicationStatus.PUBLISHED) {
524524
// Publisher reward
525525
_unLockStakeAndReward(pub.publisher, _pubId, true);
526+
_syncReviewerStatus(pub.publisher);
526527
} else if (_newStatus == PublicationStatus.SLASHED) {
527528
// Publisher Slash
528529
_unLockStakeAndSlash(pub.publisher, _pubId, true);

apps/contracts/test/09_PublishPublication.t.sol

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// SPDX-License-Identifier: UNLICENSED
22
pragma solidity ^0.8.13;
33

4+
import {Vm} from "forge-std/Vm.sol";
45
import {TestHelpers} from "./01_Helpers.t.sol";
56
import {
67

@@ -62,6 +63,7 @@ contract PublishPublicationTest is TestHelpers {
6263
_expectEmit_MemberLockedStakeOnPubId(publisher, 0, 0);
6364
_expectEmit_LockedStakeOnPubId(0, remainingLocked);
6465
_expectEmit_MemberAvailableStake(publisher, publisherStake);
66+
_expectEmit_IsAvailableReviewer(publisher, true, 0);
6567

6668
// --- 3. EXPECTATIONS: HONEST REVIEWERS ---
6769
for (uint256 i = 0; i < honestCount; i++) {
@@ -240,4 +242,53 @@ contract PublishPublicationTest is TestHelpers {
240242
vm.expectRevert(abi.encodeWithSelector(BioVerify_InvalidPublicationId.selector, nonExistentId));
241243
bioVerify.publishPublication(nonExistentId, new address[](0), new address[](0), "verdict");
242244
}
245+
246+
function test_PublishPublication_PublisherBecomesAvailableReviewer() public {
247+
// --- 1. SETUP ---
248+
_submitDefaultPub();
249+
_fillValidReviewerPool();
250+
251+
vm.prank(aiAgentAddress);
252+
uint256 requestId = bioVerify.pickReviewers(0);
253+
254+
uint256[] memory words = new uint256[](vrfNumWords);
255+
for (uint256 i = 0; i < vrfNumWords; ++i) {
256+
words[i] = i;
257+
}
258+
vrfCoordinatorMock.fulfillRandomWordsWithOverride(requestId, address(bioVerify), words);
259+
260+
// --- 2. Build all-honest reviewer arrays ---
261+
address[] memory honest = new address[](vrfNumWords);
262+
for (uint256 i = 0; i < vrfNumWords; i++) {
263+
honest[i] = makeAddr(string(abi.encodePacked("reviewer_", i)));
264+
}
265+
address[] memory negligent = new address[](0);
266+
267+
// --- 3. EXECUTION ---
268+
vm.recordLogs();
269+
vm.prank(aiAgentAddress);
270+
bioVerify.publishPublication(0, honest, negligent, FAKE_VERDICT_CID);
271+
272+
// --- 4. ASSERTION ---
273+
// Publisher's returned stake (publisherStake) >= reviewerStake,
274+
// so _syncReviewerStatus must mark them as available for future review cycles.
275+
Vm.Log[] memory entries = vm.getRecordedLogs();
276+
bytes32 eventSig = keccak256("IsAvailableReviewer(address,bool,uint256)");
277+
278+
bool foundPublisherEvent = false;
279+
for (uint256 i = 0; i < entries.length; i++) {
280+
if (entries[i].topics[0] != eventSig) continue;
281+
282+
address member = address(uint160(uint256(entries[i].topics[1])));
283+
if (member == publisher) {
284+
(bool isAvailable, uint256 newActiveReviews) = abi.decode(entries[i].data, (bool, uint256));
285+
assertEq(member, publisher, "Event reviewer must be publisher");
286+
assertTrue(isAvailable, "Publisher must become available reviewer after publication");
287+
assertEq(newActiveReviews, 0, "Publisher must have 0 active reviews");
288+
foundPublisherEvent = true;
289+
break;
290+
}
291+
}
292+
assertTrue(foundPublisherEvent, "IsAvailableReviewer event must be emitted for publisher");
293+
}
243294
}

apps/fe/tsconfig.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
"name": "next"
2020
}
2121
],
22-
"baseUrl": ".",
2322
"paths": {
2423
"@/*": ["./*"],
2524
"@packages/*": ["../../packages/*"]

tsconfig.json

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,14 @@
1313
"isolatedModules": true,
1414
"jsx": "preserve",
1515
"incremental": true,
16-
"baseUrl": ".",
1716
"paths": {
18-
"@packages/env": ["packages/env/index.ts"],
19-
"@packages/cqrs": ["packages/cqrs/index.ts"],
20-
"@packages/agents": ["packages/agents/index.ts"],
21-
"@packages/schema": ["packages/schema/index.ts"],
22-
"@packages/notifications": ["packages/notifications/index.ts"],
23-
"@packages/utils": ["packages/utils/index.ts"],
24-
"@packages/utils-server": ["packages/utils-server/index.ts"],
17+
"@packages/env": ["./packages/env/index.ts"],
18+
"@packages/cqrs": ["./packages/cqrs/index.ts"],
19+
"@packages/agents": ["./packages/agents/index.ts"],
20+
"@packages/schema": ["./packages/schema/index.ts"],
21+
"@packages/notifications": ["./packages/notifications/index.ts"],
22+
"@packages/utils": ["./packages/utils/index.ts"],
23+
"@packages/utils-server": ["./packages/utils-server/index.ts"],
2524
}
2625
},
2726
"include": ["apps/**/*", "packages/**/*"],

0 commit comments

Comments
 (0)