Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions script/offboard/ScribeZeroValue.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;

/**
* @title ScribeZeroValue
*
* @notice Static scribe oracle returning zero or reverting on read functions
*
* @dev Deployment:
* ```bash
* $ forge create script/offboarder/ScribeZeroValue.sol:ScribeZeroValue \
* --keystore $KEYSTORE \
* --password $KEYSTORE_PASSWORD \
* --rpc-url $RPC_URL \
* --verifier-url $ETHERSCAN_API_URL \
* --etherscan-api-key $ETHERSCAN_API_KEY
* ```
*
* @author Chronicle Labs, Inc
* @custom:security-contact security@chroniclelabs.org
*/
contract ScribeZeroValue {
Comment thread
pmerkleplant marked this conversation as resolved.
function read() external pure returns (uint) {
revert();
}

function tryRead() external pure returns (bool, uint) {
return (false, 0);
}

function readWithAge() external pure returns (uint, uint) {
revert();
}

function tryReadWithAge() external pure returns (bool, uint, uint) {
return (false, 0, 0);
}

// - MakerDAO Compatibility

function peek() external pure returns (uint, bool) {
return (0, false);
}

function peep() external pure returns (uint, bool) {
return (0, false);
}

// - Chainlink Compatibility

function latestRoundData()
external
pure
returns (
uint80 roundId,
int answer,
uint startedAt,
uint updatedAt,
uint80 answeredInRound
)
{
roundId = 1;
answer = 0;
startedAt = 0;
updatedAt = 0;
answeredInRound = roundId;
}

function latestAnswer() external pure returns (int) {
int answer = 0;
return answer;
Comment on lines +70 to +71

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Separated into two statements to allow forge coverage to mark the function as fully tested, ie achieve 100% test coverage.

}
}
69 changes: 69 additions & 0 deletions src/IScribeFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;

import {IScribe} from "./IScribe.sol";

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

interface IScribeFactory {
/// @dev ScribeConfig encapsulates a Scribe instance's deployment
/// configuration.
struct ScribeConfig {
string name;
bytes32 wat;
uint8 bar;
address[] authed;
address[] tolled;
LibSecp256k1.Point[] validators;
IScribe.ECDSAData[] registrationSigs;
}

/// @dev ScribeRouterConfig encapsulates a ScribeRouter instance's
/// deployment configuration.
struct ScribeRouterConfig {
string name;
address[] authed;
address[] tolled;
}

/// @notice Emitted when new Scribe instance deployed.
/// @param caller The caller's address.
/// @param scribe The deployed Scribe instance's address.
/// @param name The Scribe instance's name.
event ScribeDeployed(
address indexed caller, address indexed scribe, string name
);

/// @notice Emitted when new ScribeRouter instance deployed.
/// @param caller The caller's address.
/// @param router The deployed ScribeRouter instance's address.
/// @param name The ScribeRouter instance's name.
event ScribeRouterDeployed(
address indexed caller, address indexed router, string name
);

/// @notice Deploys a new Scribe and ScribeRouter instance.
/// @dev Only callable by toll'ed address.
/// @param scribeCfg The Scribe instance's deployment configuration.
/// @param routerCfg The ScribeRouter instance's deployment configuration.
/// @return The deployed Scribe instance's address.
/// @return The deployed ScribeRouter instance's address.
function plantScribeWithRouter(
ScribeConfig calldata scribeCfg,
ScribeRouterConfig calldata routerCfg
) external returns (address, address);

/// @notice Deploys a new Scribe instance.
/// @dev Only callable by toll'ed address.
/// @param cfg The Scribe instance's deployment configuration.
/// @return The deployed Scribe instance's address.
function plantScribe(ScribeConfig calldata cfg) external returns (address);

/// @notice Deploys a new ScribeRouter instance.
/// @dev Only callable by toll'ed address.
/// @param cfg The ScribeRouter instance's deployment configuration.
/// @return The deployed ScribeRouter instance's address.
function plantRouter(ScribeRouterConfig calldata cfg)
external
returns (address);
}
79 changes: 79 additions & 0 deletions src/IScribeRouter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;

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

interface IScribeRouter is IChronicle {
/// @notice Emitted when the scribe address is updated.
/// @param caller The caller's address.
/// @param oldScribe The old scribe address.
/// @param newScribe The new scribe address.
event ScribeUpdated(
address indexed caller, address oldScribe, address newScribe
);

/// @notice Returns the name identifier.
/// @return name The name of the oracle.
function name() external view returns (string memory name);

/// @notice Returns the wat identifier.
/// @return wat The wat of the oracle.
function wat() external view returns (bytes32 wat);

/// @notice Returns the scribe address.
/// @return scribe The scribe address.
function scribe() external view returns (address scribe);

/// @notice Updates the scribe contract.
/// @dev Only callable by auth'ed address.
/// @param scribe The scribe address.
function setScribe(address scribe) external;

// -- MakerDAO Compatibility --

/// @notice Returns the oracle's current value.
/// @custom:deprecated Use `tryRead()(bool,uint)` instead.
/// @return value The oracle's current value if it exists, zero otherwise.
/// @return isValid True if value exists, false otherwise.
function peek() external view returns (uint value, bool isValid);

/// @notice Returns the oracle's current value.
/// @custom:deprecated Use `tryRead()(bool,uint)` instead.
/// @return value The oracle's current value if it exists, zero otherwise.
/// @return isValid True if value exists, false otherwise.
function peep() external view returns (uint value, bool isValid);

// -- Chainlink Compatibility --

/// @notice Returns the number of decimals of the oracle's value.
/// @dev Provides partial compatibility with Chainlink's
/// IAggregatorV3Interface.
/// @return decimals The oracle value's number of decimals.
function decimals() external view returns (uint8 decimals);

/// @notice Returns the oracle's latest value.
/// @dev Provides partial compatibility with Chainlink's
/// IAggregatorV3Interface.
/// @return roundId 1.
/// @return answer The oracle's latest value.
/// @return startedAt 0.
/// @return updatedAt The timestamp of oracle's latest update.
/// @return answeredInRound 1.
function latestRoundData()
external
view
returns (
uint80 roundId,
int answer,
uint startedAt,
uint updatedAt,
uint80 answeredInRound
);

/// @notice Returns the oracle's latest value.
/// @dev Provides partial compatibility with Chainlink's
/// IAggregatorV3Interface.
/// @custom:deprecated See https://docs.chain.link/data-feeds/api-reference/#latestanswer.
/// @return answer The oracle's latest value.
function latestAnswer() external view returns (int answer);
}
128 changes: 128 additions & 0 deletions src/ScribeFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.16;

import {Auth} from "chronicle-std/auth/Auth.sol";
import {Toll} from "chronicle-std/toll/Toll.sol";

import {IScribeFactory} from "./IScribeFactory.sol";

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

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

/**
* @title ScribeFactory
* @custom:version 2.0.1
*
* @notice Factory contract to deploy Scribe and ScribeRouter oracle contracts
*
* @author Chronicle Labs, Inc
* @custom:security-contact security@chroniclelabs.org
*/
contract ScribeFactory is IScribeFactory, Auth, Toll {
constructor(address initialAuthed) payable Auth(initialAuthed) {}

/// @inheritdoc IScribeFactory
function plantScribeWithRouter(
ScribeConfig calldata scribeCfg,
ScribeRouterConfig calldata routerCfg
) public toll returns (address, address) {
Scribe scribe = _plantScribe(scribeCfg);
ScribeRouter router = _plantRouter(routerCfg);

scribe.kiss(address(router));
router.setScribe(address(scribe));

scribe.deny(address(this));
router.deny(address(this));

return (address(scribe), address(router));
}

/// @inheritdoc IScribeFactory
function plantScribe(ScribeConfig calldata cfg)
public
toll
returns (address)
{
Scribe scribe = _plantScribe(cfg);

scribe.deny(address(this));

return address(scribe);
}

/// @inheritdoc IScribeFactory
function plantRouter(ScribeRouterConfig calldata cfg)
public
toll
returns (address)
{
ScribeRouter router = _plantRouter(cfg);

router.deny(address(this));

return address(router);
}

// -- Internal Helpers --

function _plantScribe(ScribeConfig calldata cfg) internal returns (Scribe) {
// Deploy scribe.
Scribe scribe = new Scribe(address(this), cfg.wat);
emit ScribeDeployed(msg.sender, address(scribe), cfg.name);

// Lift validators and set bar.
scribe.lift(cfg.validators, cfg.registrationSigs);
scribe.setBar(cfg.bar);

// Rely authed and kiss tolled.
for (uint i; i < cfg.authed.length; i++) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

guess its possible to plant a "pre-bricked" Scribe if cfg.authed isn't checked to not be empty. operationally more of a headache than anything I guess

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, imho bearable though taking into account how automated the deployments are nowadays.

scribe.rely(cfg.authed[i]);
}
for (uint i; i < cfg.tolled.length; i++) {
scribe.kiss(cfg.tolled[i]);
}

// Kiss zero address.
scribe.kiss(address(0));

return scribe;
}

function _plantRouter(ScribeRouterConfig calldata cfg)
internal
returns (ScribeRouter)
{
// Deploy router.
ScribeRouter router = new ScribeRouter(address(this), cfg.name);
emit ScribeRouterDeployed(msg.sender, address(router), cfg.name);

// Rely authed and kiss tolled.
for (uint i; i < cfg.authed.length; i++) {
router.rely(cfg.authed[i]);
}
for (uint i; i < cfg.tolled.length; i++) {
router.kiss(cfg.tolled[i]);
}

// Kiss zero address.
router.kiss(address(0));

return router;
}

// -- Overridden Toll Functions --

/// @dev Defines authorization for IToll's authenticated functions.
function toll_auth() internal override(Toll) auth {}
}

/**
* @dev Contract overwrite to deploy contract instances with specific naming.
*/
contract ScribeFactory_COUNTER is ScribeFactory {

@jar-o jar-o Apr 20, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doesn't follow instances/ pattern? guess that emerged with VAO ...

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah correct, I tried to not update the pattern here. IIRC instances/ evolved somewhere after scribe but before vao 🤷

// @todo ^^^^^^^ Adjust counter of Factory instance.
constructor(address initialAuthed) ScribeFactory(initialAuthed) {}
}
Loading
Loading