Skip to content

Commit

Permalink
Scribe LST extension
Browse files Browse the repository at this point in the history
  • Loading branch information
jar-o committed Jan 15, 2024
1 parent 41f25a8 commit 69072d8
Show file tree
Hide file tree
Showing 6 changed files with 276 additions and 0 deletions.
8 changes: 8 additions & 0 deletions src/extensions/IRateSource.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity >=0.8.0;

// https://github.com/marsfoundation/sparklend-advanced/blob/277ea9d9ad7faf330b88198c9c6de979a2fad561/src/interfaces/IRateSource.sol

interface IRateSource {
function getAPR() external view returns (uint);
}
37 changes: 37 additions & 0 deletions src/extensions/ScribeLST.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.16;

import {IChronicle} from "chronicle-std/IChronicle.sol";

import {IRateSource} from "./IRateSource.sol";
import {Scribe} from "../Scribe.sol";

/**
* @title ScribeLST
*
* @notice Schnorr based Oracle with onchain fault resolution for Liquid
* Staking Tokens.
*/
contract ScribeLST is Scribe, IRateSource {
constructor(address initialAuthed_, bytes32 wat_)
Scribe(initialAuthed_, wat_)
{}

function getAPR() external view returns (uint) {
uint val = _pokeData.val;
require(val != 0);
return val;
}
}

/**
* @dev Contract overwrite to deploy contract instances with specific naming.
*
* For more info, see docs/Deployment.md.
*/
contract Chronicle_BASE_QUOTE_COUNTER is ScribeLST {
// @todo ^^^^ ^^^^^ ^^^^^^^ Adjust name of Scribe instance.
constructor(address initialAuthed, bytes32 wat_)
ScribeLST(initialAuthed, wat_)
{}
}
37 changes: 37 additions & 0 deletions src/extensions/ScribeOptimisticLST.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.16;

import {IChronicle} from "chronicle-std/IChronicle.sol";

import {IRateSource} from "./IRateSource.sol";
import {ScribeOptimistic} from "../ScribeOptimistic.sol";

/**
* @title ScribeOptimisticLST
*
* @notice Schnorr based optimistic Oracle with onchain fault resolution for
* Liquid Staking Tokens.
*/
contract ScribeOptimisticLST is ScribeOptimistic, IRateSource {
constructor(address initialAuthed_, bytes32 wat_)
ScribeOptimistic(initialAuthed_, wat_)
{}

function getAPR() external view returns (uint) {
uint val = _currentPokeData().val;
require(val != 0);
return val;
}
}

/**
* @dev Contract overwrite to deploy contract instances with specific naming.
*
* For more info, see docs/Deployment.md.
*/
contract Chronicle_BASE_QUOTE_COUNTER is ScribeOptimisticLST {
// @todo ^^^^ ^^^^^ ^^^^^^^ Adjust name of Scribe instance.
constructor(address initialAuthed, bytes32 wat_)
ScribeOptimisticLST(initialAuthed, wat_)
{}
}
88 changes: 88 additions & 0 deletions test/IScribeOptimisticTestLST.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;

import {Test} from "forge-std/Test.sol";

import {IToll} from "chronicle-std/toll/IToll.sol";

import {IScribe} from "src/IScribe.sol";
import {ScribeOptimisticLST} from "src/extensions/ScribeOptimisticLST.sol";

import {LibSecp256k1} from "src/libs/LibSecp256k1.sol";

import {LibFeed} from "script/libs/LibFeed.sol";

abstract contract IScribeOptimisticTestLST is Test {
using LibFeed for LibFeed.Feed;
using LibFeed for LibFeed.Feed[];

ScribeOptimisticLST private scribe;

bytes32 internal WAT;
bytes32 internal FEED_REGISTRATION_MESSAGE;

function setUp(address payable scribe_) internal virtual {
scribe = ScribeOptimisticLST(scribe_);

// Cache constants.
WAT = scribe.wat();
FEED_REGISTRATION_MESSAGE = scribe.feedRegistrationMessage();

// Toll address(this).
IToll(address(scribe)).kiss(address(this));
}

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(FEED_REGISTRATION_MESSAGE)
);
}

privKey++;
}

return feeds;
}

function test_getAPR() public {
// An actual value, i.e.
// CFG_ETH_RPC_URLS="$ETH_RPC_URL" go1.21.1 run ./cmd/gofer data 'LIDO_LST/7DAYS'
uint val = 3592236075648471881;

LibFeed.Feed[] memory feeds = _liftFeeds(scribe.bar());

IScribe.PokeData memory pokeData;
pokeData.val = uint128(val);
pokeData.age = 1;

IScribe.SchnorrData memory schnorrData;
schnorrData = feeds.signSchnorr(scribe.constructPokeMessage(pokeData));

scribe.poke(pokeData, schnorrData);
assertEq(scribe.getAPR(), val);
assertEq(scribe.getAPR(), scribe.read());
}
}
88 changes: 88 additions & 0 deletions test/IScribeTestLST.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;

import {Test} from "forge-std/Test.sol";

import {IToll} from "chronicle-std/toll/IToll.sol";

import {IScribe} from "src/IScribe.sol";
import {ScribeLST} from "src/extensions/ScribeLST.sol";

import {LibSecp256k1} from "src/libs/LibSecp256k1.sol";

import {LibFeed} from "script/libs/LibFeed.sol";

abstract contract IScribeTestLST is Test {
using LibFeed for LibFeed.Feed;
using LibFeed for LibFeed.Feed[];

ScribeLST private scribe;

bytes32 internal WAT;
bytes32 internal FEED_REGISTRATION_MESSAGE;

function setUp(address scribe_) internal virtual {
scribe = ScribeLST(scribe_);

// Cache constants.
WAT = scribe.wat();
FEED_REGISTRATION_MESSAGE = scribe.feedRegistrationMessage();

// Toll address(this).
IToll(address(scribe)).kiss(address(this));
}

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(FEED_REGISTRATION_MESSAGE)
);
}

privKey++;
}

return feeds;
}

function test_getAPR() public {
// An actual value, i.e.
// CFG_ETH_RPC_URLS="$ETH_RPC_URL" go1.21.1 run ./cmd/gofer data 'LIDO_LST/1DAY'
uint val = 3386003474307116517;

LibFeed.Feed[] memory feeds = _liftFeeds(scribe.bar());

IScribe.PokeData memory pokeData;
pokeData.val = uint128(val);
pokeData.age = 1;

IScribe.SchnorrData memory schnorrData;
schnorrData = feeds.signSchnorr(scribe.constructPokeMessage(pokeData));

scribe.poke(pokeData, schnorrData);
assertEq(scribe.getAPR(), val);
assertEq(scribe.getAPR(), scribe.read());
}
}
18 changes: 18 additions & 0 deletions test/Runner.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@ pragma solidity ^0.8.16;

import {Scribe} from "src/Scribe.sol";
import {ScribeInspectable} from "./inspectable/ScribeInspectable.sol";
import {ScribeLST} from "src/extensions/ScribeLST.sol";
import {ScribeOptimisticLST} from "src/extensions/ScribeOptimisticLST.sol";
import {IScribe} from "src/IScribe.sol";

import {IScribeTest} from "./IScribeTest.sol";
import {IScribeTestLST} from "./IScribeTestLST.sol";
import {IScribeOptimisticTestLST} from "./IScribeOptimisticTestLST.sol";
import {IScribeInvariantTest} from "./invariants/IScribeInvariantTest.sol";
import {ScribeHandler} from "./invariants/ScribeHandler.sol";

Expand All @@ -26,6 +30,12 @@ contract ScribeInvariantTest is IScribeInvariantTest {
}
}

contract ScribeTestLST is IScribeTestLST {
function setUp() public {
setUp(address(new ScribeLST(address(this), "ETH/USD")));
}
}

// -- Test: Optimistic Scribe --

import {ScribeOptimistic} from "src/ScribeOptimistic.sol";
Expand All @@ -39,6 +49,14 @@ contract ScribeOptimisticTest is IScribeOptimisticTest {
}
}

contract ScribeOptimisticTestLST is IScribeOptimisticTestLST {
function setUp() public {
setUp(
payable(address(new ScribeOptimisticLST(address(this), "ETH/USD")))
);
}
}

// -- Test: Libraries --

import {LibSecp256k1Test as LibSecp256k1Test_} from "./LibSecp256k1Test.sol";
Expand Down

0 comments on commit 69072d8

Please sign in to comment.