Skip to content

Commit 3e47e53

Browse files
authored
src: Allow uniform staleness monitoring (#8)
1 parent 8da39a6 commit 3e47e53

3 files changed

Lines changed: 65 additions & 1 deletion

File tree

src/IUScribe.sol

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,12 @@ interface IUScribe {
158158
/// @return string The oracle's name.
159159
function name() external view returns (string memory);
160160

161+
/// @notice Returns the timestamp of the latest poke.
162+
/// @dev This timestamp is updated on every poke and allows uniform
163+
/// staleness monitoring accross uscribe instances.
164+
/// @return uint The timestamp of the latest poke.
165+
function latestPoke() external view returns (uint);
166+
161167
/// @notice Returns whether address `who` is a validator for Schnorr
162168
/// verification.
163169
/// @param who The address to check.

src/UScribe.sol

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ abstract contract UScribe is IUScribe, Auth {
2828

2929
bytes4 internal constant _NO_ERR = bytes4(0);
3030

31+
/// @inheritdoc IUScribe
3132
bytes32 public immutable wat;
3233

3334
// Note that strings cannot be marked as immutable.
@@ -37,6 +38,8 @@ abstract contract UScribe is IUScribe, Auth {
3738
//--------------------------------------------------------------------------
3839
// Storage
3940

41+
uint48 private _latestPoke;
42+
4043
struct SchnorrStorage {
4144
LibSecp256k1.Point[256] pubKeys;
4245
uint8 bar;
@@ -100,7 +103,9 @@ abstract contract UScribe is IUScribe, Auth {
100103
///
101104
/// Protections against replayability issues MAY be including a nonce
102105
/// in the payload or only accepting payloads with strictly
103-
/// monotonically increasing timestamps.
106+
/// monotonically increasing timestamps. The `latestPoke()(uint)`
107+
/// function can be used by consumers to access the timestamp of the
108+
/// last poke.
104109
///
105110
/// To protect against cross-chain replayability issues the payload
106111
/// MAY be expected to include the chain's id.
@@ -140,6 +145,9 @@ abstract contract UScribe is IUScribe, Auth {
140145
if (err != _NO_ERR) {
141146
revert PokeError_ConsumerRejectedPayload(err);
142147
}
148+
149+
// Update latest poke timestamp once poke accepted by consumer.
150+
_latestPoke = uint48(block.timestamp);
143151
}
144152

145153
/// @inheritdoc IUScribe
@@ -169,6 +177,9 @@ abstract contract UScribe is IUScribe, Auth {
169177
if (err != _NO_ERR) {
170178
revert PokeError_ConsumerRejectedPayload(err);
171179
}
180+
181+
// Update latest poke timestamp once poke accepted by consumer.
182+
_latestPoke = uint48(block.timestamp);
172183
}
173184

174185
//--------------------------------------------------------------------------
@@ -409,6 +420,14 @@ abstract contract UScribe is IUScribe, Auth {
409420
//--------------------------------------------------------------------------
410421
// Public View Functions
411422

423+
/// @inheritdoc IUScribe
424+
///
425+
/// @dev Note that function is public to grant read-only access to
426+
/// downstream consumer.
427+
function latestPoke() public view returns (uint) {
428+
return _latestPoke;
429+
}
430+
412431
//----------------------------------
413432
// SchnorrStorage
414433

test/UScribe.t.sol

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,9 @@ contract UScribeTest is Test {
184184
// No validators lifted for Schnorr or ECDSA.
185185
assertEq(uscribe.validatorsSchnorr().length, 0);
186186
assertEq(uscribe.validatorsECDSA().length, 0);
187+
188+
// Latest poke timestamp is zero.
189+
assertEq(uscribe.latestPoke(), 0);
187190
}
188191

189192
function test_Deployment_FailsIf_NameIsEmpty() public {
@@ -231,6 +234,9 @@ contract UScribeTest is Test {
231234

232235
// Poke uPokeData with Schnorr musig.
233236
uscribe.poke(uPokeData, schnorr);
237+
238+
// Expect latestPoke timestamp to be updated.
239+
assertEq(uscribe.latestPoke(), vm.getBlockTimestamp());
234240
}
235241

236242
function testFuzz_pokeSchnorr_FailsIf_VerificationFailed_DueTo_BarNotReached(
@@ -271,6 +277,9 @@ contract UScribeTest is Test {
271277
)
272278
);
273279
uscribe.poke(uPokeData, schnorr);
280+
281+
// Expect latestPoke timestamp to not be updated.
282+
assertEq(uscribe.latestPoke(), 0);
274283
}
275284

276285
function testFuzz_pokeSchnorr_FailsIf_VerificationFailed_DueTo_SignatureInvalid(
@@ -314,6 +323,9 @@ contract UScribeTest is Test {
314323
)
315324
);
316325
uscribe.poke(uPokeData, schnorr);
326+
327+
// Expect latestPoke timestamp to not be updated.
328+
assertEq(uscribe.latestPoke(), 0);
317329
}
318330

319331
function testFuzz_pokeSchnorr_FailsIf_VerificationFailed_DueTo_ValidatorInvalid(
@@ -364,6 +376,9 @@ contract UScribeTest is Test {
364376
)
365377
);
366378
uscribe.poke(uPokeData, schnorr);
379+
380+
// Expect latestPoke timestamp to not be updated.
381+
assertEq(uscribe.latestPoke(), 0);
367382
}
368383

369384
function testFuzz_pokeSchnorr_FailsIf_VerificationFailed_DueTo_DoubleSigningAttempted(
@@ -407,6 +422,9 @@ contract UScribeTest is Test {
407422
)
408423
);
409424
uscribe.poke(uPokeData, schnorr);
425+
426+
// Expect latestPoke timestamp to not be updated.
427+
assertEq(uscribe.latestPoke(), 0);
410428
}
411429

412430
function testFuzz_pokeSchnorr_FailsIf_ConsumerRejectedPayload(
@@ -444,6 +462,9 @@ contract UScribeTest is Test {
444462
)
445463
);
446464
uscribe.poke(uPokeData, schnorr);
465+
466+
// Expect latestPoke timestamp to not be updated.
467+
assertEq(uscribe.latestPoke(), 0);
447468
}
448469

449470
//----------------------------------
@@ -505,6 +526,9 @@ contract UScribeTest is Test {
505526
// Poke uPokeData with Schnorr FROST.
506527
uscribe.poke(uPokeData, schnorrData);
507528

529+
// Expect latestPoke timestamp to be updated.
530+
assertEq(uscribe.latestPoke(), vm.getBlockTimestamp());
531+
508532
// Expect poke of mutated uPokeData with Schnorr FROST to fail.
509533
vm.expectRevert(
510534
abi.encodeWithSelector(
@@ -549,6 +573,9 @@ contract UScribeTest is Test {
549573

550574
// Poke uPokeData with list of ECDSA signatures.
551575
uscribe.poke(uPokeData, ecdsas);
576+
577+
// Expect latestPoke timestamp to be updated.
578+
assertEq(uscribe.latestPoke(), vm.getBlockTimestamp());
552579
}
553580

554581
function testFuzz_pokeECDSA_FailsIf_VerificationFailed_DueTo_BarNotReached(
@@ -583,6 +610,9 @@ contract UScribeTest is Test {
583610
)
584611
);
585612
uscribe.poke(uPokeData, ecdsas);
613+
614+
// Expect latestPoke timestamp to not be updated.
615+
assertEq(uscribe.latestPoke(), 0);
586616
}
587617

588618
function testFuzz_pokeECDSA_FailsIf_VerificationFailed_DueTo_SignatureInvalid(
@@ -624,6 +654,9 @@ contract UScribeTest is Test {
624654
)
625655
);
626656
uscribe.poke(uPokeData, ecdsas);
657+
658+
// Expect latestPoke timestamp to not be updated.
659+
assertEq(uscribe.latestPoke(), 0);
627660
}
628661

629662
function testFuzz_pokeECDSA_FailsIf_VerificationFailed_DueTo_DoubleSigningAttempted(
@@ -663,6 +696,9 @@ contract UScribeTest is Test {
663696
)
664697
);
665698
uscribe.poke(uPokeData, ecdsas);
699+
700+
// Expect latestPoke timestamp to not be updated.
701+
assertEq(uscribe.latestPoke(), 0);
666702
}
667703

668704
function testFuzz_pokeECDSA_FailsIf_ConsumerRejectedPayload(
@@ -697,6 +733,9 @@ contract UScribeTest is Test {
697733
)
698734
);
699735
uscribe.poke(uPokeData, ecdsas);
736+
737+
// Expect latestPoke timestamp to not be updated.
738+
assertEq(uscribe.latestPoke(), 0);
700739
}
701740

702741
//--------------------------------------------------------------------------

0 commit comments

Comments
 (0)