From d33bd707a63fa6483d00039032ce741930051c9a Mon Sep 17 00:00:00 2001 From: merkleplant Date: Tue, 31 Mar 2026 15:04:23 +0200 Subject: [PATCH 1/3] lint: Forge fmt --- script/Scribe.s.sol | 4 +- script/ScribeOptimistic.s.sol | 19 +- script/benchmarks/ScribeBenchmark.s.sol | 3 +- .../ScribeOptimisticBenchmark.s.sol | 9 +- script/dev/ScribeOptimisticTester.s.sol | 15 +- script/libs/LibSchnorrExtended.sol | 5 +- script/rescue/Rescuer.sol | 4 +- src/IScribe.sol | 3 + src/IScribeOptimistic.sol | 3 + src/Scribe.sol | 5 +- src/ScribeOptimistic.sol | 3 + src/extensions/IScribeLST.sol | 3 + src/extensions/IScribeOptimisticLST.sol | 3 + src/extensions/ScribeLST.sol | 3 + src/extensions/ScribeOptimisticLST.sol | 3 + .../external_/interfaces/IRateSource.sol | 4 + src/libs/LibSchnorr.sol | 3 + src/libs/LibSecp256k1.sol | 4 + test/EVMTest.sol | 6 +- test/IScribeOptimisticTest.sol | 48 +-- test/IScribeRouterTest.sol | 281 ++++++++++++++++++ test/IScribeTest.sol | 10 +- test/Runner.t.sol | 5 +- test/invariants/IScribeInvariantTest.sol | 4 +- test/invariants/ScribeHandler.sol | 3 +- 25 files changed, 384 insertions(+), 69 deletions(-) create mode 100644 test/IScribeRouterTest.sol diff --git a/script/Scribe.s.sol b/script/Scribe.s.sol index f6651a8..6592440 100644 --- a/script/Scribe.s.sol +++ b/script/Scribe.s.sol @@ -357,7 +357,9 @@ contract ScribeScript is Script { // Require self to be deactivated. { vm.prank(address(0)); - (bool ok, /*val*/ ) = IScribe(self).tryRead(); + ( + bool ok, /*val*/ + ) = IScribe(self).tryRead(); require(!ok, "Instance not deactivated: read() does not fail"); require( diff --git a/script/ScribeOptimistic.s.sol b/script/ScribeOptimistic.s.sol index bf5a7b0..8a62fcd 100644 --- a/script/ScribeOptimistic.s.sol +++ b/script/ScribeOptimistic.s.sol @@ -7,8 +7,9 @@ import {IToll} from "chronicle-std/toll/IToll.sol"; import {IScribe} from "src/IScribe.sol"; import {IScribeOptimistic} from "src/IScribeOptimistic.sol"; -import {Chronicle_BASE_QUOTE_COUNTER as ScribeOptimistic} from - "src/ScribeOptimistic.sol"; +import { + Chronicle_BASE_QUOTE_COUNTER as ScribeOptimistic +} from "src/ScribeOptimistic.sol"; // @todo ^^^^ ^^^^^ ^^^^^^^ Adjust name of Scribe instance. import {LibSecp256k1} from "src/libs/LibSecp256k1.sol"; @@ -134,7 +135,9 @@ contract ScribeOptimisticScript is ScribeScript { // Require self to be deactivated. { vm.prank(address(0)); - (bool ok, /*val*/ ) = IScribe(self).tryRead(); + ( + bool ok, /*val*/ + ) = IScribe(self).tryRead(); require(!ok, "Instance not deactivated: read() does not fail"); require( @@ -173,16 +176,14 @@ contract ScribeOptimisticScript is ScribeScript { IScribe.SchnorrData(bytes32(0), address(0), hex""); // Construct opPokeMessage. - bytes32 opPokeMessage = IScribeOptimistic(self).constructOpPokeMessage( - pokeData, schnorrData - ); + bytes32 opPokeMessage = IScribeOptimistic(self) + .constructOpPokeMessage(pokeData, schnorrData); // Let feed sign opPokeMessage. IScribe.ECDSAData memory opPokeSig = feed.signECDSA(opPokeMessage); // Rescue ETH via rescuer contract. - Rescuer(payable(rescuer)).suck( - self, feed.pubKey, registrationSig, pokeDataAge, opPokeSig - ); + Rescuer(payable(rescuer)) + .suck(self, feed.pubKey, registrationSig, pokeDataAge, opPokeSig); } } diff --git a/script/benchmarks/ScribeBenchmark.s.sol b/script/benchmarks/ScribeBenchmark.s.sol index 1605d63..3745f3f 100644 --- a/script/benchmarks/ScribeBenchmark.s.sol +++ b/script/benchmarks/ScribeBenchmark.s.sol @@ -94,8 +94,7 @@ contract ScribeBenchmark is Script { // Create pokeData. // Note to use max value for val to have highest possible gas costs. IScribe.PokeData memory pokeData = IScribe.PokeData({ - val: type(uint128).max, - age: uint32(block.timestamp) + val: type(uint128).max, age: uint32(block.timestamp) }); // Create schnorrData. diff --git a/script/benchmarks/ScribeOptimisticBenchmark.s.sol b/script/benchmarks/ScribeOptimisticBenchmark.s.sol index dbda8ce..76c5fd4 100644 --- a/script/benchmarks/ScribeOptimisticBenchmark.s.sol +++ b/script/benchmarks/ScribeOptimisticBenchmark.s.sol @@ -113,8 +113,7 @@ contract ScribeOptimisticBenchmark is Script { // Create pokeData. // Note to use max value for val to have highest possible gas costs. IScribe.PokeData memory pokeData = IScribe.PokeData({ - val: type(uint128).max, - age: uint32(block.timestamp) + val: type(uint128).max, age: uint32(block.timestamp) }); // Create schnorrData. @@ -135,8 +134,7 @@ contract ScribeOptimisticBenchmark is Script { // Create pokeData. // Note to use max value for val to have highest possible gas costs. IScribe.PokeData memory pokeData = IScribe.PokeData({ - val: type(uint128).max, - age: uint32(block.timestamp) + val: type(uint128).max, age: uint32(block.timestamp) }); // Create schnorrData. @@ -164,8 +162,7 @@ contract ScribeOptimisticBenchmark is Script { // Create pokeData. // Note to use max value for val to have highest possible gas costs. IScribe.PokeData memory pokeData = IScribe.PokeData({ - val: type(uint128).max, - age: uint32(block.timestamp) + val: type(uint128).max, age: uint32(block.timestamp) }); // Create schnorrData. diff --git a/script/dev/ScribeOptimisticTester.s.sol b/script/dev/ScribeOptimisticTester.s.sol index b2d7b04..3354c7f 100644 --- a/script/dev/ScribeOptimisticTester.s.sol +++ b/script/dev/ScribeOptimisticTester.s.sol @@ -78,9 +78,8 @@ contract ScribeOptimisticTesterScript is ScribeTesterScript { IScribe.ECDSAData memory ecdsaData; ecdsaData = signer.signECDSA( - IScribeOptimistic(self).constructOpPokeMessage( - pokeData, schnorrData - ) + IScribeOptimistic(self) + .constructOpPokeMessage(pokeData, schnorrData) ); vm.startBroadcast(); @@ -155,9 +154,8 @@ contract ScribeOptimisticTesterScript is ScribeTesterScript { IScribe.ECDSAData memory ecdsaData; ecdsaData = signer.signECDSA( - IScribeOptimistic(self).constructOpPokeMessage( - pokeData, schnorrData - ) + IScribeOptimistic(self) + .constructOpPokeMessage(pokeData, schnorrData) ); vm.startBroadcast(); @@ -210,9 +208,8 @@ contract ScribeOptimisticTesterScript is ScribeTesterScript { bytes32 pokeMessage = IScribe(self).constructPokeMessage(pokeData); // Check whether schnorrData is not acceptable. - bool ok = IScribe(self).isAcceptableSchnorrSignatureNow( - pokeMessage, schnorrData - ); + bool ok = IScribe(self) + .isAcceptableSchnorrSignatureNow(pokeMessage, schnorrData); if (ok) { console2.log( "Schnorr signature is acceptable: expecting opChallenge to be unsuccessful" diff --git a/script/libs/LibSchnorrExtended.sol b/script/libs/LibSchnorrExtended.sol index ec57316..d4bbcfa 100644 --- a/script/libs/LibSchnorrExtended.sol +++ b/script/libs/LibSchnorrExtended.sol @@ -175,8 +175,9 @@ library LibSchnorrExtended { returns (uint) { // k = H(x ‖ m) mod Q - return uint(keccak256(abi.encodePacked(privKey, message))) - % LibSecp256k1.Q(); + return + uint(keccak256(abi.encodePacked(privKey, message))) + % LibSecp256k1.Q(); } function computeNoncePublicKey(uint nonce) diff --git a/script/rescue/Rescuer.sol b/script/rescue/Rescuer.sol index 69e7ff2..b867bde 100644 --- a/script/rescue/Rescuer.sol +++ b/script/rescue/Rescuer.sol @@ -90,9 +90,7 @@ contract Rescuer is Auth { // Construct invalid Schnorr signature. IScribe.SchnorrData memory schnorrSig = IScribe.SchnorrData({ - signature: bytes32(0), - commitment: address(0), - feedIds: hex"" + signature: bytes32(0), commitment: address(0), feedIds: hex"" }); // Lift validator. diff --git a/src/IScribe.sol b/src/IScribe.sol index 3e66818..3870dff 100644 --- a/src/IScribe.sol +++ b/src/IScribe.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.16; +// forgefmt: disable-start import {IChronicle} from "chronicle-std/IChronicle.sol"; @@ -243,3 +244,5 @@ interface IScribe is IChronicle { /// @return isValid True if value exists, false otherwise. function peep() external view returns (uint value, bool isValid); } + +// forgefmt: disable-end diff --git a/src/IScribeOptimistic.sol b/src/IScribeOptimistic.sol index 9cc3ccb..a24d6f9 100644 --- a/src/IScribeOptimistic.sol +++ b/src/IScribeOptimistic.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.16; +// forgefmt: disable-start import {IScribe} from "./IScribe.sol"; @@ -174,3 +175,5 @@ interface IScribeOptimistic is IScribe { /// @param maxChallengeReward The value to update maxChallengeReward to. function setMaxChallengeReward(uint maxChallengeReward) external; } + +// forgefmt: disable-end diff --git a/src/Scribe.sol b/src/Scribe.sol index 567cec4..f68a6f9 100644 --- a/src/Scribe.sol +++ b/src/Scribe.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.16; +// forgefmt: disable-start import {IChronicle} from "chronicle-std/IChronicle.sol"; import {Auth} from "chronicle-std/auth/Auth.sol"; @@ -150,7 +151,7 @@ contract Scribe is IScribe, Auth, Toll { function _verifySchnorrSignature( bytes32 message, SchnorrData calldata schnorrData - ) internal view returns (bool, bytes memory) { + ) internal virtual view returns (bool, bytes memory) { // Let feedPubKey be the currently processed feed's public key. LibSecp256k1.Point memory feedPubKey; // Let feedId be the currently processed feed's id. @@ -526,3 +527,5 @@ contract Chronicle_BASE_QUOTE_COUNTER is Scribe { Scribe(initialAuthed, wat_) {} } + +// forgefmt: disable-end diff --git a/src/ScribeOptimistic.sol b/src/ScribeOptimistic.sol index b3f411f..d097b11 100644 --- a/src/ScribeOptimistic.sol +++ b/src/ScribeOptimistic.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.16; +// forgefmt: disable-start import {IChronicle} from "chronicle-std/IChronicle.sol"; @@ -599,3 +600,5 @@ contract Chronicle_BASE_QUOTE_COUNTER is ScribeOptimistic { ScribeOptimistic(initialAuthed, wat_) {} } + +// forgefmt: disable-end diff --git a/src/extensions/IScribeLST.sol b/src/extensions/IScribeLST.sol index 5bd4171..f6b55dc 100644 --- a/src/extensions/IScribeLST.sol +++ b/src/extensions/IScribeLST.sol @@ -1,8 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.16; +// forgefmt: disable-start import {IScribe} from "../IScribe.sol"; import {IRateSource} from "./external_/interfaces/IRateSource.sol"; interface IScribeLST is IScribe, IRateSource {} + +// forgefmt: disable-end diff --git a/src/extensions/IScribeOptimisticLST.sol b/src/extensions/IScribeOptimisticLST.sol index 4384c10..60caab2 100644 --- a/src/extensions/IScribeOptimisticLST.sol +++ b/src/extensions/IScribeOptimisticLST.sol @@ -1,8 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.16; +// forgefmt: disable-start import {IScribeOptimistic} from "../IScribeOptimistic.sol"; import {IRateSource} from "./external_/interfaces/IRateSource.sol"; interface IScribeOptimisticLST is IScribeOptimistic, IRateSource {} + +// forgefmt: disable-end diff --git a/src/extensions/ScribeLST.sol b/src/extensions/ScribeLST.sol index d6a3b72..004ab2e 100644 --- a/src/extensions/ScribeLST.sol +++ b/src/extensions/ScribeLST.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.16; +// forgefmt: disable-start import {IScribeLST} from "./IScribeLST.sol"; import {IRateSource} from "./external_/interfaces/IRateSource.sol"; @@ -36,3 +37,5 @@ contract Chronicle_BASE_QUOTE_COUNTER is ScribeLST { ScribeLST(initialAuthed, wat_) {} } + +// forgefmt: disable-end diff --git a/src/extensions/ScribeOptimisticLST.sol b/src/extensions/ScribeOptimisticLST.sol index ebc316f..b1b0078 100644 --- a/src/extensions/ScribeOptimisticLST.sol +++ b/src/extensions/ScribeOptimisticLST.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.16; +// forgefmt: disable-start import {IScribeOptimisticLST} from "./IScribeOptimisticLST.sol"; import {IRateSource} from "./external_/interfaces/IRateSource.sol"; @@ -36,3 +37,5 @@ contract Chronicle_BASE_QUOTE_COUNTER is ScribeOptimisticLST { ScribeOptimisticLST(initialAuthed, wat_) {} } + +// forgefmt: disable-end diff --git a/src/extensions/external_/interfaces/IRateSource.sol b/src/extensions/external_/interfaces/IRateSource.sol index 4f592d8..ba805c2 100644 --- a/src/extensions/external_/interfaces/IRateSource.sol +++ b/src/extensions/external_/interfaces/IRateSource.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: AGPL-3.0 pragma solidity >=0.8.0; +// forgefmt: disable-start + /** * @dev Interest rate oracle interface from [Spark](https://spark.fi/). * @@ -11,3 +13,5 @@ interface IRateSource { /// @return The oracle's current APR value. function getAPR() external view returns (uint); } + +// forgefmt: disable-end diff --git a/src/libs/LibSchnorr.sol b/src/libs/LibSchnorr.sol index bc05a71..fc31091 100644 --- a/src/libs/LibSchnorr.sol +++ b/src/libs/LibSchnorr.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.16; +// forgefmt: disable-start import {LibSecp256k1} from "./LibSecp256k1.sol"; @@ -101,3 +102,5 @@ library LibSchnorr { return commitment == recovered; } } + +// forgefmt: disable-end diff --git a/src/libs/LibSecp256k1.sol b/src/libs/LibSecp256k1.sol index 3cb0198..20c04fe 100644 --- a/src/libs/LibSecp256k1.sol +++ b/src/libs/LibSecp256k1.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.16; +// forgefmt: disable-start + /** * @title LibSecp256k1 * @@ -359,3 +361,5 @@ library LibSecp256k1 { return t; } } + +// forgefmt: disable-end diff --git a/test/EVMTest.sol b/test/EVMTest.sol index 8d9fe73..bd87b89 100644 --- a/test/EVMTest.sol +++ b/test/EVMTest.sol @@ -10,9 +10,9 @@ abstract contract EVMTest is Test { /// index returns 0. /// Note that ScribeOptimistic::opChallenge() requires such an /// expression to _not revert_. - function testFuzz_calldataload_ReadingNonExistingCalldataReturnsZero( - uint index - ) public { + function testFuzz_calldataload_ReadingNonExistingCalldataReturnsZero(uint index) + public + { uint minIndex; assembly ("memory-safe") { minIndex := calldatasize() diff --git a/test/IScribeOptimisticTest.sol b/test/IScribeOptimisticTest.sol index b76e652..110e5cc 100644 --- a/test/IScribeOptimisticTest.sol +++ b/test/IScribeOptimisticTest.sol @@ -318,9 +318,9 @@ abstract contract IScribeOptimisticTest is IScribeTest { opScribe.opPoke(pokeData, schnorrData, ecdsaData); } - function testFuzz_opPoke_FailsIf_opPokeDataInChallengePeriodExists( - uint warpSeed - ) public { + function testFuzz_opPoke_FailsIf_opPokeDataInChallengePeriodExists(uint warpSeed) + public + { LibFeed.Feed[] memory feeds = _liftFeeds(opScribe.bar()); IScribe.PokeData memory pokeData; @@ -349,9 +349,9 @@ abstract contract IScribeOptimisticTest is IScribeTest { } // See audits/Cantina@v2.0.0.pdf. - function testFuzz_opPoke_FailsIf_BarNotReached_DueTo_GasAttack( - uint feedIdsLengthSeed - ) public { + function testFuzz_opPoke_FailsIf_BarNotReached_DueTo_GasAttack(uint feedIdsLengthSeed) + public + { // Note to stay reasonable with calldata load. uint feedIdsLength = _bound(feedIdsLengthSeed, uint(type(uint8).max) + 1, 1_000); @@ -647,8 +647,8 @@ abstract contract IScribeOptimisticTest is IScribeTest { // 0000000000000000000000000000000000000000000000000000000000000060 // offset(feedIds) // 0000000000000000000000000000000000000000000000000000000000000002 // length(feedIds) // 2b68000000000000000000000000000000000000000000000000000000000000 // feedIds - bytes memory customCalldata = ( - hex"8928a1f8" // selector + bytes memory customCalldata = + (hex"8928a1f8" // selector hex"0000000000000000000000000000000000000000000000000000000000000020" // struct offset hex"fecd661c0731ca99672edb7303a072da3c2b8342e714c2f2a90639b966298958" // signature hex"000000000000000000000000026428bf84a659a2d371be1e705613d89d93f78f" // commitment @@ -656,8 +656,7 @@ abstract contract IScribeOptimisticTest is IScribeTest { hex"0000000000000000000000000000000000000000000000000000000000000002" // injected(feedIds).length hex"2b2b000000000000000000000000000000000000000000000000000000000000" // injected(feedIds) (double sign) hex"0000000000000000000000000000000000000000000000000000000000000002" // length(feedIds) - hex"2b68000000000000000000000000000000000000000000000000000000000000" - ); // feedIds + hex"2b68000000000000000000000000000000000000000000000000000000000000"); // feedIds (bool success, bytes memory retData) = address(opScribe).call(customCalldata); @@ -737,8 +736,9 @@ abstract contract IScribeOptimisticTest is IScribeTest { opScribe.setOpChallengePeriod(0); } - function test_setOpChallengePeriod_DropsFinalizedOpPoke_If_NonFinalizedAfterUpdate( - ) public { + function test_setOpChallengePeriod_DropsFinalizedOpPoke_If_NonFinalizedAfterUpdate() + public + { LibFeed.Feed[] memory feeds = _liftFeeds(opScribe.bar()); // Execute opPoke. @@ -807,9 +807,9 @@ abstract contract IScribeOptimisticTest is IScribeTest { return feeds; } - function testFuzz_setOpChallengePeriod_IsAfterAuthedActionProtected( - bool opPokeFinalized - ) public { + function testFuzz_setOpChallengePeriod_IsAfterAuthedActionProtected(bool opPokeFinalized) + public + { IScribe.PokeData memory pokeData; pokeData.val = 1; pokeData.age = uint32(block.timestamp); @@ -826,9 +826,9 @@ abstract contract IScribeOptimisticTest is IScribeTest { opScribe.setOpChallengePeriod(1); } - function testFuzz_drop_Single_IsAfterAuthedActionProtected( - bool opPokeFinalized - ) public { + function testFuzz_drop_Single_IsAfterAuthedActionProtected(bool opPokeFinalized) + public + { IScribe.PokeData memory pokeData; pokeData.val = 1; pokeData.age = uint32(block.timestamp); @@ -845,9 +845,9 @@ abstract contract IScribeOptimisticTest is IScribeTest { opScribe.drop(feeds[0].id); } - function testFuzz_drop_Multiple_IsAfterAuthedActionProtected( - bool opPokeFinalized - ) public { + function testFuzz_drop_Multiple_IsAfterAuthedActionProtected(bool opPokeFinalized) + public + { IScribe.PokeData memory pokeData; pokeData.val = 1; pokeData.age = uint32(block.timestamp); @@ -901,9 +901,9 @@ abstract contract IScribeOptimisticTest is IScribeTest { /// 4: finalized opPoke() + poke() -> val is poke()'s val /// 5: finalized opPoke() + finalized opPoke() -> val is second finalized opPoke()'s val /// 6: non-finalized opPoke() + poke() -> val is poke()'s val - function testFuzz_afterAuthedAction_ProvidesValue_If_MoreThanOncePoked( - uint pathSeed - ) public { + function testFuzz_afterAuthedAction_ProvidesValue_If_MoreThanOncePoked(uint pathSeed) + public + { LibFeed.Feed[] memory feeds = _liftFeeds(opScribe.bar()); uint path = _bound(pathSeed, 0, 6); diff --git a/test/IScribeRouterTest.sol b/test/IScribeRouterTest.sol new file mode 100644 index 0000000..a425f5c --- /dev/null +++ b/test/IScribeRouterTest.sol @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.16; + +import {Test} from "forge-std/Test.sol"; + +import {IAuth} from "chronicle-std/auth/IAuth.sol"; +import {IToll} from "chronicle-std/toll/IToll.sol"; + +import {Scribe} from "src/Scribe.sol"; +import {IScribe} from "src/IScribe.sol"; + +import {IScribeRouter} from "src/IScribeRouter.sol"; + +import {LibFeed} from "script/libs/LibFeed.sol"; +import {LibOracleSuite} from "script/libs/LibOracleSuite.sol"; + +abstract contract IScribeRouterTest is Test { + using LibFeed for LibFeed.Feed; + using LibFeed for LibFeed.Feed[]; + + IScribeRouter private router; + IScribe private scribe; + + // Events copied from IScribeRouter. + event ScribeUpdated( + address indexed caller, address oldScribe, address newScribe + ); + + function setUp(address router_) internal virtual { + // Set router and toll address(this). + router = IScribeRouter(router_); + IToll(address(router)).kiss(address(this)); + + // Deploy scribe and toll router and address(this). + scribe = new Scribe(address(this), "ETH/USD"); + IToll(address(scribe)).kiss(address(router)); + IToll(address(scribe)).kiss(address(this)); + + // Set scribe on router. + router.setScribe(address(scribe)); + } + + function _liftFeeds(uint8 numberFeeds) + internal + returns (LibFeed.Feed[] memory) + { + LibFeed.Feed[] memory feeds = new LibFeed.Feed[](uint(numberFeeds)); + + // Note to not start with privKey=1. This is because the sum of public + // keys would evaluate to: + // pubKeyOf(1) + pubKeyOf(2) + pubKeyOf(3) + ... + // = pubKeyOf(3) + pubKeyOf(3) + ... + // Note that pubKeyOf(3) would be doubled. Doubling is not supported by + // LibSecp256k1 as this would indicate a double-signing attack. + uint privKey = 2; + uint bloom; + uint ctr; + while (ctr != numberFeeds) { + LibFeed.Feed memory feed = LibFeed.newFeed({privKey: privKey}); + + // Check whether feed with id already created, if not create and + // lift. + if (bloom & (1 << feed.id) == 0) { + bloom |= 1 << feed.id; + + feeds[ctr++] = feed; + scribe.lift( + feed.pubKey, + feed.signECDSA(scribe.feedRegistrationMessage()) + ); + } + + privKey++; + } + + return feeds; + } + + function _poke(uint128 val) internal { + LibFeed.Feed[] memory feeds = _liftFeeds(scribe.bar()); + + IScribe.PokeData memory pokeData; + pokeData.val = val; + pokeData.age = uint32(block.timestamp); + + IScribe.SchnorrData memory schnorrData; + schnorrData = feeds.signSchnorr(scribe.constructPokeMessage(pokeData)); + + scribe.poke(pokeData, schnorrData); + } + + function _checkReadFunctions(uint wantVal, uint wantAge) internal { + bool ok; + uint gotVal; + uint gotAge; + + assertEq(router.read(), wantVal); + assertEq(scribe.read(), wantVal); + + (ok, gotVal) = router.tryRead(); + assertEq(gotVal, wantVal); + assertTrue(ok); + (ok, gotVal) = scribe.tryRead(); + assertEq(gotVal, wantVal); + assertTrue(ok); + + (gotVal, gotAge) = router.readWithAge(); + assertEq(gotVal, wantVal); + assertEq(gotAge, wantAge); + (gotVal, gotAge) = scribe.readWithAge(); + assertEq(gotVal, wantVal); + assertEq(gotAge, wantAge); + + (ok, gotVal, gotAge) = router.tryReadWithAge(); + assertTrue(ok); + assertEq(gotVal, wantVal); + assertEq(gotAge, wantAge); + (ok, gotVal, gotAge) = scribe.tryReadWithAge(); + assertTrue(ok); + assertEq(gotVal, wantVal); + assertEq(gotAge, wantAge); + + (gotVal, ok) = router.peek(); + assertEq(gotVal, wantVal); + assertTrue(ok); + (gotVal, ok) = scribe.peek(); + assertEq(gotVal, wantVal); + assertTrue(ok); + + (gotVal, ok) = router.peep(); + assertEq(gotVal, wantVal); + assertTrue(ok); + (gotVal, ok) = scribe.peep(); + assertEq(gotVal, wantVal); + assertTrue(ok); + + uint80 roundId; + int answer; + uint startedAt; + uint updatedAt; + uint80 answeredInRound; + (roundId, answer, startedAt, updatedAt, answeredInRound) = + router.latestRoundData(); + assertEq(uint(roundId), 1); + assertEq(uint(answer), wantVal); + assertEq(startedAt, 0); + assertEq(updatedAt, wantAge); + assertEq(uint(answeredInRound), 1); + (roundId, answer, startedAt, updatedAt, answeredInRound) = + scribe.latestRoundData(); + assertEq(uint(roundId), 1); + assertEq(uint(answer), wantVal); + assertEq(startedAt, 0); + assertEq(updatedAt, wantAge); + assertEq(uint(answeredInRound), 1); + + answer = router.latestAnswer(); + assertEq(uint(answer), wantVal); + answer = scribe.latestAnswer(); + assertEq(uint(answer), wantVal); + } + + // -- Test: Deployment -- + + function test_Deployment() public { + // Address given as constructor argument is auth'ed. + assertTrue(IAuth(address(router)).authed(address(this))); + + // Name and wat are set. + assertEq(router.name(), "ETH/USD"); + assertEq(router.wat(), keccak256(bytes("ETH/USD"))); + } + + // -- Test: setScribe -- + + function testFuzz_setScribe(address newScribe) public { + address current = router.scribe(); + if (current != newScribe) { + vm.expectEmit(); + emit ScribeUpdated(address(this), current, newScribe); + } + + router.setScribe(newScribe); + assertEq(router.scribe(), newScribe); + } + + function test_setScribe_isAuthProtected() public { + vm.prank(address(0xbeef)); + vm.expectRevert( + abi.encodeWithSelector( + IAuth.NotAuthorized.selector, address(0xbeef) + ) + ); + router.setScribe(address(0)); + } + + // -- Test: Read Functions -- + + function testFuzz_readFunctions(uint128 val) public { + vm.assume(val != 0); + _poke(val); + _checkReadFunctions(val, block.timestamp); + } + + function test_readFunctions_ValZero() public { + // TODO: ... + } + + // -- Test: Toll Protected Functions -- + + // - IChronicle Functions + + function test_read_isTollProtected() public { + vm.prank(address(0xbeef)); + vm.expectRevert( + abi.encodeWithSelector(IToll.NotTolled.selector, address(0xbeef)) + ); + router.read(); + } + + function test_tryRead_isTollProtected() public { + vm.prank(address(0xbeef)); + vm.expectRevert( + abi.encodeWithSelector(IToll.NotTolled.selector, address(0xbeef)) + ); + router.tryRead(); + } + + function test_readWithAge_isTollProtected() public { + vm.prank(address(0xbeef)); + vm.expectRevert( + abi.encodeWithSelector(IToll.NotTolled.selector, address(0xbeef)) + ); + router.readWithAge(); + } + + function test_tryReadWithAge_isTollProtected() public { + vm.prank(address(0xbeef)); + vm.expectRevert( + abi.encodeWithSelector(IToll.NotTolled.selector, address(0xbeef)) + ); + router.tryReadWithAge(); + } + + // - MakerDAO Compatibility + + function test_peek_isTollProtected() public { + vm.prank(address(0xbeef)); + vm.expectRevert( + abi.encodeWithSelector(IToll.NotTolled.selector, address(0xbeef)) + ); + router.peek(); + } + + function test_peep_isTollProtected() public { + vm.prank(address(0xbeef)); + vm.expectRevert( + abi.encodeWithSelector(IToll.NotTolled.selector, address(0xbeef)) + ); + router.peep(); + } + + // - Chainlink Compatibility + + function test_latestRoundData_isTollProtected() public { + vm.prank(address(0xbeef)); + vm.expectRevert( + abi.encodeWithSelector(IToll.NotTolled.selector, address(0xbeef)) + ); + router.latestRoundData(); + } + + function test_latestAnswer_isTollProtected() public { + vm.prank(address(0xbeef)); + vm.expectRevert( + abi.encodeWithSelector(IToll.NotTolled.selector, address(0xbeef)) + ); + router.latestAnswer(); + } +} + diff --git a/test/IScribeTest.sol b/test/IScribeTest.sol index ac62902..50476c9 100644 --- a/test/IScribeTest.sol +++ b/test/IScribeTest.sol @@ -273,7 +273,9 @@ abstract contract IScribeTest is Test { LibFeed.Feed memory feed = LibFeed.newFeed({privKey: privKey}); // Don't run test if bad luck and feed already lifted. - (bool isFeed, /*feedAddr*/ ) = scribe.feeds(feed.id); + ( + bool isFeed, /*feedAddr*/ + ) = scribe.feeds(feed.id); if (isFeed) return; // Let index ∊ [0, bar). @@ -290,9 +292,9 @@ abstract contract IScribeTest is Test { assertFalse(ok); } - function testFuzz_isAcceptableSchnorrSignatureNow_FailsIf_SignatureInvalid( - uint barSeed - ) public { + function testFuzz_isAcceptableSchnorrSignatureNow_FailsIf_SignatureInvalid(uint barSeed) + public + { // Let bar ∊ [1, 256). uint8 bar = uint8(_bound(barSeed, 1, 256 - 1)); scribe.setBar(bar); diff --git a/test/Runner.t.sol b/test/Runner.t.sol index ded137b..32397eb 100644 --- a/test/Runner.t.sol +++ b/test/Runner.t.sol @@ -53,8 +53,9 @@ contract ScribeOptimisticTest is IScribeOptimisticTest { // -- Extensions import {ScribeOptimisticLST} from "src/extensions/ScribeOptimisticLST.sol"; -import {IScribeOptimisticLSTTest} from - "./extensions/IScribeOptimisticLSTTest.sol"; +import { + IScribeOptimisticLSTTest +} from "./extensions/IScribeOptimisticLSTTest.sol"; contract ScribeOptimisticLSTTest is IScribeOptimisticLSTTest { function setUp() public { diff --git a/test/invariants/IScribeInvariantTest.sol b/test/invariants/IScribeInvariantTest.sol index eba3d85..8965d0c 100644 --- a/test/invariants/IScribeInvariantTest.sol +++ b/test/invariants/IScribeInvariantTest.sol @@ -36,7 +36,9 @@ abstract contract IScribeInvariantTest is Test { // Set handler as target contract. targetSelector( - FuzzSelector({addr: address(handler), selectors: _targetSelectors()}) + FuzzSelector({ + addr: address(handler), selectors: _targetSelectors() + }) ); targetContract(address(handler)); } diff --git a/test/invariants/ScribeHandler.sol b/test/invariants/ScribeHandler.sol index 4d7aff9..c9753ab 100644 --- a/test/invariants/ScribeHandler.sol +++ b/test/invariants/ScribeHandler.sol @@ -96,8 +96,7 @@ contract ScribeHandler is CommonBase, StdUtils { // Create pokeData. IScribe.PokeData memory pokeData = IScribe.PokeData({ - val: _randPokeDataVal(valSeed), - age: _randPokeDataAge(ageSeed) + val: _randPokeDataVal(valSeed), age: _randPokeDataAge(ageSeed) }); bytes32 pokeMessage = scribe.constructPokeMessage(pokeData); From 777e803406e0d1401bf516c0bae66710cfcefa36 Mon Sep 17 00:00:00 2001 From: merkleplant Date: Tue, 31 Mar 2026 15:06:34 +0200 Subject: [PATCH 2/3] lint --- test/IScribeRouterTest.sol | 281 ------------------------------------- 1 file changed, 281 deletions(-) delete mode 100644 test/IScribeRouterTest.sol diff --git a/test/IScribeRouterTest.sol b/test/IScribeRouterTest.sol deleted file mode 100644 index a425f5c..0000000 --- a/test/IScribeRouterTest.sol +++ /dev/null @@ -1,281 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.16; - -import {Test} from "forge-std/Test.sol"; - -import {IAuth} from "chronicle-std/auth/IAuth.sol"; -import {IToll} from "chronicle-std/toll/IToll.sol"; - -import {Scribe} from "src/Scribe.sol"; -import {IScribe} from "src/IScribe.sol"; - -import {IScribeRouter} from "src/IScribeRouter.sol"; - -import {LibFeed} from "script/libs/LibFeed.sol"; -import {LibOracleSuite} from "script/libs/LibOracleSuite.sol"; - -abstract contract IScribeRouterTest is Test { - using LibFeed for LibFeed.Feed; - using LibFeed for LibFeed.Feed[]; - - IScribeRouter private router; - IScribe private scribe; - - // Events copied from IScribeRouter. - event ScribeUpdated( - address indexed caller, address oldScribe, address newScribe - ); - - function setUp(address router_) internal virtual { - // Set router and toll address(this). - router = IScribeRouter(router_); - IToll(address(router)).kiss(address(this)); - - // Deploy scribe and toll router and address(this). - scribe = new Scribe(address(this), "ETH/USD"); - IToll(address(scribe)).kiss(address(router)); - IToll(address(scribe)).kiss(address(this)); - - // Set scribe on router. - router.setScribe(address(scribe)); - } - - function _liftFeeds(uint8 numberFeeds) - internal - returns (LibFeed.Feed[] memory) - { - LibFeed.Feed[] memory feeds = new LibFeed.Feed[](uint(numberFeeds)); - - // Note to not start with privKey=1. This is because the sum of public - // keys would evaluate to: - // pubKeyOf(1) + pubKeyOf(2) + pubKeyOf(3) + ... - // = pubKeyOf(3) + pubKeyOf(3) + ... - // Note that pubKeyOf(3) would be doubled. Doubling is not supported by - // LibSecp256k1 as this would indicate a double-signing attack. - uint privKey = 2; - uint bloom; - uint ctr; - while (ctr != numberFeeds) { - LibFeed.Feed memory feed = LibFeed.newFeed({privKey: privKey}); - - // Check whether feed with id already created, if not create and - // lift. - if (bloom & (1 << feed.id) == 0) { - bloom |= 1 << feed.id; - - feeds[ctr++] = feed; - scribe.lift( - feed.pubKey, - feed.signECDSA(scribe.feedRegistrationMessage()) - ); - } - - privKey++; - } - - return feeds; - } - - function _poke(uint128 val) internal { - LibFeed.Feed[] memory feeds = _liftFeeds(scribe.bar()); - - IScribe.PokeData memory pokeData; - pokeData.val = val; - pokeData.age = uint32(block.timestamp); - - IScribe.SchnorrData memory schnorrData; - schnorrData = feeds.signSchnorr(scribe.constructPokeMessage(pokeData)); - - scribe.poke(pokeData, schnorrData); - } - - function _checkReadFunctions(uint wantVal, uint wantAge) internal { - bool ok; - uint gotVal; - uint gotAge; - - assertEq(router.read(), wantVal); - assertEq(scribe.read(), wantVal); - - (ok, gotVal) = router.tryRead(); - assertEq(gotVal, wantVal); - assertTrue(ok); - (ok, gotVal) = scribe.tryRead(); - assertEq(gotVal, wantVal); - assertTrue(ok); - - (gotVal, gotAge) = router.readWithAge(); - assertEq(gotVal, wantVal); - assertEq(gotAge, wantAge); - (gotVal, gotAge) = scribe.readWithAge(); - assertEq(gotVal, wantVal); - assertEq(gotAge, wantAge); - - (ok, gotVal, gotAge) = router.tryReadWithAge(); - assertTrue(ok); - assertEq(gotVal, wantVal); - assertEq(gotAge, wantAge); - (ok, gotVal, gotAge) = scribe.tryReadWithAge(); - assertTrue(ok); - assertEq(gotVal, wantVal); - assertEq(gotAge, wantAge); - - (gotVal, ok) = router.peek(); - assertEq(gotVal, wantVal); - assertTrue(ok); - (gotVal, ok) = scribe.peek(); - assertEq(gotVal, wantVal); - assertTrue(ok); - - (gotVal, ok) = router.peep(); - assertEq(gotVal, wantVal); - assertTrue(ok); - (gotVal, ok) = scribe.peep(); - assertEq(gotVal, wantVal); - assertTrue(ok); - - uint80 roundId; - int answer; - uint startedAt; - uint updatedAt; - uint80 answeredInRound; - (roundId, answer, startedAt, updatedAt, answeredInRound) = - router.latestRoundData(); - assertEq(uint(roundId), 1); - assertEq(uint(answer), wantVal); - assertEq(startedAt, 0); - assertEq(updatedAt, wantAge); - assertEq(uint(answeredInRound), 1); - (roundId, answer, startedAt, updatedAt, answeredInRound) = - scribe.latestRoundData(); - assertEq(uint(roundId), 1); - assertEq(uint(answer), wantVal); - assertEq(startedAt, 0); - assertEq(updatedAt, wantAge); - assertEq(uint(answeredInRound), 1); - - answer = router.latestAnswer(); - assertEq(uint(answer), wantVal); - answer = scribe.latestAnswer(); - assertEq(uint(answer), wantVal); - } - - // -- Test: Deployment -- - - function test_Deployment() public { - // Address given as constructor argument is auth'ed. - assertTrue(IAuth(address(router)).authed(address(this))); - - // Name and wat are set. - assertEq(router.name(), "ETH/USD"); - assertEq(router.wat(), keccak256(bytes("ETH/USD"))); - } - - // -- Test: setScribe -- - - function testFuzz_setScribe(address newScribe) public { - address current = router.scribe(); - if (current != newScribe) { - vm.expectEmit(); - emit ScribeUpdated(address(this), current, newScribe); - } - - router.setScribe(newScribe); - assertEq(router.scribe(), newScribe); - } - - function test_setScribe_isAuthProtected() public { - vm.prank(address(0xbeef)); - vm.expectRevert( - abi.encodeWithSelector( - IAuth.NotAuthorized.selector, address(0xbeef) - ) - ); - router.setScribe(address(0)); - } - - // -- Test: Read Functions -- - - function testFuzz_readFunctions(uint128 val) public { - vm.assume(val != 0); - _poke(val); - _checkReadFunctions(val, block.timestamp); - } - - function test_readFunctions_ValZero() public { - // TODO: ... - } - - // -- Test: Toll Protected Functions -- - - // - IChronicle Functions - - function test_read_isTollProtected() public { - vm.prank(address(0xbeef)); - vm.expectRevert( - abi.encodeWithSelector(IToll.NotTolled.selector, address(0xbeef)) - ); - router.read(); - } - - function test_tryRead_isTollProtected() public { - vm.prank(address(0xbeef)); - vm.expectRevert( - abi.encodeWithSelector(IToll.NotTolled.selector, address(0xbeef)) - ); - router.tryRead(); - } - - function test_readWithAge_isTollProtected() public { - vm.prank(address(0xbeef)); - vm.expectRevert( - abi.encodeWithSelector(IToll.NotTolled.selector, address(0xbeef)) - ); - router.readWithAge(); - } - - function test_tryReadWithAge_isTollProtected() public { - vm.prank(address(0xbeef)); - vm.expectRevert( - abi.encodeWithSelector(IToll.NotTolled.selector, address(0xbeef)) - ); - router.tryReadWithAge(); - } - - // - MakerDAO Compatibility - - function test_peek_isTollProtected() public { - vm.prank(address(0xbeef)); - vm.expectRevert( - abi.encodeWithSelector(IToll.NotTolled.selector, address(0xbeef)) - ); - router.peek(); - } - - function test_peep_isTollProtected() public { - vm.prank(address(0xbeef)); - vm.expectRevert( - abi.encodeWithSelector(IToll.NotTolled.selector, address(0xbeef)) - ); - router.peep(); - } - - // - Chainlink Compatibility - - function test_latestRoundData_isTollProtected() public { - vm.prank(address(0xbeef)); - vm.expectRevert( - abi.encodeWithSelector(IToll.NotTolled.selector, address(0xbeef)) - ); - router.latestRoundData(); - } - - function test_latestAnswer_isTollProtected() public { - vm.prank(address(0xbeef)); - vm.expectRevert( - abi.encodeWithSelector(IToll.NotTolled.selector, address(0xbeef)) - ); - router.latestAnswer(); - } -} - From c539631588913c236a19b0bb80e2e8f9cfc283f8 Mon Sep 17 00:00:00 2001 From: merkleplant Date: Tue, 31 Mar 2026 15:55:21 +0200 Subject: [PATCH 3/3] remove typo --- src/Scribe.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Scribe.sol b/src/Scribe.sol index f68a6f9..da29e69 100644 --- a/src/Scribe.sol +++ b/src/Scribe.sol @@ -151,7 +151,7 @@ contract Scribe is IScribe, Auth, Toll { function _verifySchnorrSignature( bytes32 message, SchnorrData calldata schnorrData - ) internal virtual view returns (bool, bytes memory) { + ) internal view returns (bool, bytes memory) { // Let feedPubKey be the currently processed feed's public key. LibSecp256k1.Point memory feedPubKey; // Let feedId be the currently processed feed's id.