// SPDX-License-Identifier: MIT pragma solidity 0.8.24; import { Test } from "forge-std/Test.sol"; import { StreetCredV3 } from "../../contracts/reputation/StreetCredV3.sol"; contract StreetCredV3Test is Test { address internal constant OWNER = address(0xA11CE); address internal constant ATTESTER = address(0xA); address internal constant SPOOFED_ATTESTER = address(0xB); StreetCredV3 internal streetCred; function setUp() external { streetCred = new StreetCredV3(OWNER); vm.prank(OWNER); streetCred.setAttester(ATTESTER, true); } function test_RejectsSpoofedSignalAttester() external { StreetCredV3.Signal memory signal = StreetCredV3.Signal({ schemaVersion: 1, weightBps: 10_000, evidenceCommitment: keccak256("evidence"), classification: 2, attester: SPOOFED_ATTESTER, observedAt: uint64(block.timestamp), expiresAt: 0, negative: false }); vm.prank(ATTESTER); vm.expectRevert(StreetCredV3.AttesterMismatch.selector); streetCred.addSignal(keccak256("subject"), signal); } function testFuzz_RecordsAuthenticatedSignal(uint32 weight) external { weight = uint32(bound(weight, 0, 10_000)); StreetCredV3.Signal memory signal = StreetCredV3.Signal({ schemaVersion: 1, weightBps: weight, evidenceCommitment: keccak256("evidence"), classification: 2, attester: ATTESTER, observedAt: uint64(block.timestamp), expiresAt: 0, negative: false }); bytes32 subject = keccak256("subject"); vm.prank(ATTESTER); streetCred.addSignal(subject, signal); assertEq(streetCred.signalAt(subject, 0).attester, ATTESTER); } function test_rejectsZeroSubjectEvidenceFutureObservationAndUnknownClass() external { StreetCredV3.Signal memory signal = _signal(100, false); vm.startPrank(ATTESTER); vm.expectRevert(StreetCredV3.InvalidSignal.selector); streetCred.addSignal(bytes32(0), signal); signal.evidenceCommitment = bytes32(0); vm.expectRevert(StreetCredV3.InvalidSignal.selector); streetCred.addSignal(keccak256("subject"), signal); signal = _signal(100, false); signal.observedAt = uint64(block.timestamp + 1); vm.expectRevert(StreetCredV3.InvalidSignal.selector); streetCred.addSignal(keccak256("subject"), signal); signal = _signal(100, false); signal.classification = 7; vm.expectRevert(StreetCredV3.InvalidSignal.selector); streetCred.addSignal(keccak256("subject"), signal); vm.stopPrank(); } function test_scoreIsOrderIndependentAndEvidenceRootIsPopulated() external { bytes32 first = keccak256("first"); bytes32 second = keccak256("second"); vm.startPrank(ATTESTER); streetCred.addSignal(first, _signal(8_000, false)); streetCred.addSignal(first, _signal(3_000, true)); streetCred.addSignal(second, _signal(3_000, true)); streetCred.addSignal(second, _signal(8_000, false)); streetCred.recompute(first, 2); streetCred.recompute(second, 2); vm.stopPrank(); (uint32 schemaA, uint32 scoreA, uint32 countA, bytes32 rootA,,) = streetCred.scoreOf(first); (, uint32 scoreB, uint32 countB, bytes32 rootB,,) = streetCred.scoreOf(second); assertEq(schemaA, 1); assertEq(scoreA, scoreB); assertEq(countA, 2); assertEq(countB, 2); assertTrue(rootA != bytes32(0)); assertTrue(rootB != bytes32(0)); } function test_signalExpiringExactlyNowIsExcluded() external { StreetCredV3.Signal memory signal = _signal(10_000, false); signal.expiresAt = uint64(block.timestamp + 1); bytes32 subject = keccak256("expiry"); vm.prank(ATTESTER); streetCred.addSignal(subject, signal); vm.warp(block.timestamp + 1); vm.prank(ATTESTER); streetCred.recompute(subject, 1); (, uint32 score, uint32 count, bytes32 root,,) = streetCred.scoreOf(subject); assertEq(score, 0); assertEq(count, 0); assertEq(root, bytes32(0)); } function _signal(uint32 weight, bool negative) private view returns (StreetCredV3.Signal memory) { return StreetCredV3.Signal({ schemaVersion: 1, weightBps: weight, evidenceCommitment: keccak256(abi.encode(weight, negative)), classification: 2, attester: ATTESTER, observedAt: uint64(block.timestamp), expiresAt: 0, negative: negative }); } }