// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;
import {IAuth} from "./IAuth.sol";
/**
* @title Auth Module
*
* @dev The `Auth` contract module provides a basic access control mechanism,
* where a set of addresses are granted access to protected functions.
* These addresses are said to be _auth'ed_.
*
* Initially, the address given as constructor argument is the only address
* auth'ed. Through the `rely(address)` and `deny(address)` functions,
* auth'ed callers are able to grant/renounce auth to/from addresses.
*
* This module is used through inheritance. It will make available the
* modifier `auth`, which can be applied to functions to restrict their
* use to only auth'ed callers.
*/
abstract contract Auth is IAuth {
/// @dev Mapping storing whether address is auth'ed.
/// @custom:invariant Image of mapping is {0, 1}.
/// ∀x ∊ Address: _wards[x] ∊ {0, 1}
/// @custom:invariant Only address given as constructor argument is authenticated after deployment.
/// deploy(initialAuthed) → (∀x ∊ Address: _wards[x] == 1 → x == initialAuthed)
/// @custom:invariant Only functions `rely` and `deny` may mutate the mapping's state.
/// ∀x ∊ Address: preTx(_wards[x]) != postTx(_wards[x])
/// → (msg.sig == "rely" ∨ msg.sig == "deny")
/// @custom:invariant Mapping's state may only be mutated by authenticated caller.
/// ∀x ∊ Address: preTx(_wards[x]) != postTx(_wards[x]) → _wards[msg.sender] = 1
mapping(address => uint) private _wards;
/// @dev List of addresses possibly being auth'ed.
/// @dev May contain duplicates.
/// @dev May contain addresses not being auth'ed anymore.
/// @custom:invariant Every address being auth'ed once is element of the list.
/// ∀x ∊ Address: authed(x) -> x ∊ _wardsTouched
address[] private _wardsTouched;
/// @dev Ensures caller is auth'ed.
modifier auth() {
assembly ("memory-safe") {
// Compute slot of _wards[msg.sender].
mstore(0x00, caller())
mstore(0x20, _wards.slot)
let slot := keccak256(0x00, 0x40)
// Revert if caller not auth'ed.
let isAuthed := sload(slot)
if iszero(isAuthed) {
// Store selector of `NotAuthorized(address)`.
mstore(0x00, 0x4a0bfec1)
// Store msg.sender.
mstore(0x20, caller())
// Revert with (offset, size).
revert(0x1c, 0x24)
}
}
_;
}
constructor(address initialAuthed) {
_wards[initialAuthed] = 1;
_wardsTouched.push(initialAuthed);
// Note to use address(0) as caller to indicate address was auth'ed
// during deployment.
emit AuthGranted(address(0), initialAuthed);
}
/// @inheritdoc IAuth
function rely(address who) external auth {
if (_wards[who] == 1) return;
_wards[who] = 1;
_wardsTouched.push(who);
emit AuthGranted(msg.sender, who);
}
/// @inheritdoc IAuth
function deny(address who) external auth {
if (_wards[who] == 0) return;
_wards[who] = 0;
emit AuthRenounced(msg.sender, who);
}
/// @inheritdoc IAuth
function authed(address who) public view returns (bool) {
return _wards[who] == 1;
}
/// @inheritdoc IAuth
/// @custom:invariant Only contains auth'ed addresses.
/// ∀x ∊ authed(): _wards[x] == 1
/// @custom:invariant Contains all auth'ed addresses.
/// ∀x ∊ Address: _wards[x] == 1 → x ∊ authed()
function authed() public view returns (address[] memory) {
// Initiate array with upper limit length.
address[] memory wardsList = new address[](_wardsTouched.length);
// Iterate through all possible auth'ed addresses.
uint ctr;
for (uint i; i < wardsList.length; i++) {
// Add address only if still auth'ed.
if (_wards[_wardsTouched[i]] == 1) {
wardsList[ctr++] = _wardsTouched[i];
}
}
// Set length of array to number of auth'ed addresses actually included.
assembly ("memory-safe") {
mstore(wardsList, ctr)
}
return wardsList;
}
/// @inheritdoc IAuth
function wards(address who) public view returns (uint) {
return _wards[who];
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;
import {Auth} from "chronicle-std/auth/Auth.sol";
import {IConfigRegistry} from "./IConfigRegistry.sol";
/**
* @title ConfigRegistry
*
* @notice Registry linking feeds to their offchain provided configs
*
* @dev Note that this registry is based on the assumption that each feed has a
* unique highest-order byte identifier. For more info, see [FeedRegistry].
*
* @dev In order to provide maximal flexibility for feed configs, the registry
* manages each feed's config separately, i.e. each feed can have its own
* config.
*
* @dev Note that due to internal technicalities the registry has a global upper
* limit for config updates of 65,535 - 1.
*
* Once this limit is reached, no further config updates can be performed!
*
* @custom:reference [FeedRegistry]: https://github.com/chronicleprotocol/feed-registry
*/
contract ConfigRegistry is IConfigRegistry, Auth {
struct Config {
string url;
bytes32 contentHash;
uint32 age;
}
// -- Storage --
/// @dev Statically allocated array holding the feeds' pointers to their
/// respective config.
///
/// @dev Note that pointers are of size uint16. Therefore, the registry
/// supports up to 65,535 - 1 config updates.
uint16[256] internal _ptrs;
/// @dev Append-only array holding Config structs.
///
/// @custom:invariant _configs[0] = NULL
/// @custom:invariant _configs.length <= type(uint16).max
Config[] internal _cfgs;
// -- Constructor --
constructor(address initialAuthed) Auth(initialAuthed) {
// Ensure uninitialized, ie zero, pointers are invalid.
_cfgs.push(Config("", bytes32(0), uint32(0)));
}
// -- Public Read Functions --
/// @inheritdoc IConfigRegistry
function configs(address feed)
external
view
returns (string memory, bytes32, uint)
{
// Derive feed's feedId.
uint8 feedId = uint8(uint(uint160(feed)) >> 152);
// Load feed's config pointer from storage.
uint16 ptr = _ptrs[feedId];
// Revert if pointer is zero.
if (ptr == 0) {
revert ConfigDoesNotExist(feedId);
}
// Load feed's config into memory.
Config memory cfg = _cfgs[ptr];
// assert(cfg.age != 0);
// Return feed config's url, content hash, and age.
return (cfg.url, cfg.contentHash, cfg.age);
}
// -- Auth'ed Functions --
/// @inheritdoc IConfigRegistry
function setConfig(
uint feedsBloom,
string calldata url,
bytes32 contentHash
) external auth {
require(_cfgs.length < type(uint16).max);
// Add config to storage and get pointer.
_cfgs.push(Config(url, contentHash, uint32(block.timestamp)));
uint16 ptr = uint16(_cfgs.length - 1);
// Update config pointer for each feed included in feedsBloom.
for (uint i; i < 256; i++) {
if (feedsBloom & (1 << i) != 0) {
_ptrs[i] = ptr;
}
}
emit ConfigUpdated({caller: msg.sender, feedsBloom: feedsBloom});
}
/// @inheritdoc IConfigRegistry
function unsetConfig(uint feedsBloom) external auth {
// Set config pointer to zero for each feed included in feedsBloom.
for (uint i; i < 256; i++) {
if (feedsBloom & (1 << i) != 0) {
delete _ptrs[i];
}
}
emit ConfigRemoved({caller: msg.sender, feedsBloom: feedsBloom});
}
}
/**
* @dev Contract overwrite to deploy contract instances with specific naming.
*
* For more info, see docs/Deployment.md.
*/
contract ConfigRegistry_1 is ConfigRegistry {
constructor(address initialAuthed) ConfigRegistry(initialAuthed) {}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;
interface IAuth {
/// @notice Thrown by protected function if caller not auth'ed.
/// @param caller The caller's address.
error NotAuthorized(address caller);
/// @notice Emitted when auth granted to address.
/// @param caller The caller's address.
/// @param who The address auth got granted to.
event AuthGranted(address indexed caller, address indexed who);
/// @notice Emitted when auth renounced from address.
/// @param caller The caller's address.
/// @param who The address auth got renounced from.
event AuthRenounced(address indexed caller, address indexed who);
/// @notice Grants address `who` auth.
/// @dev Only callable by auth'ed address.
/// @param who The address to grant auth.
function rely(address who) external;
/// @notice Renounces address `who`'s auth.
/// @dev Only callable by auth'ed address.
/// @param who The address to renounce auth.
function deny(address who) external;
/// @notice Returns whether address `who` is auth'ed.
/// @param who The address to check.
/// @return True if `who` is auth'ed, false otherwise.
function authed(address who) external view returns (bool);
/// @notice Returns full list of addresses granted auth.
/// @dev May contain duplicates.
/// @return List of addresses granted auth.
function authed() external view returns (address[] memory);
/// @notice Returns whether address `who` is auth'ed.
/// @custom:deprecated Use `authed(address)(bool)` instead.
/// @param who The address to check.
/// @return 1 if `who` is auth'ed, 0 otherwise.
function wards(address who) external view returns (uint);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;
interface IConfigRegistry {
/// @notice Thrown if no config exists for feed with id `feedId`.
error ConfigDoesNotExist(uint8 feedId);
/// @notice Emitted when config for feeds `feedsBloom` updated.
/// @param caller The caller's address.
/// @param feedsBloom The feeds encoded via bloom mechanism which's config
/// got updated.
event ConfigUpdated(address indexed caller, uint indexed feedsBloom);
/// @notice Emitted when config for feeds `feedsBloom` removed.
/// @param caller The caller's address.
/// @param feedsBloom The feeds encoded via bloom mechanism which's config
/// got removed.
event ConfigRemoved(address indexed caller, uint indexed feedsBloom);
// -- Public Read Functions --
/// @notice Returns the url and metadata of feed `feed`'s config.
///
/// @dev Reverts if:
/// - Config does not exist for feed `feed`
///
/// @param feed The feed address.
/// @return url The url of the feed's config.
/// @return contentHash The keccak256 hash of the feed config's content.
/// @return age The age of the config.
function configs(address feed)
external
view
returns (string memory url, bytes32 contentHash, uint age);
// -- Auth'ed Functions --
/// @notice Updates the config's url and config's content hash for feeds
/// encoded in bloom filter `feedsBloom`.
///
/// @dev Only callable by auth'ed addresses.
///
/// @dev Reverts if:
/// - Maximum number of config updates performed
///
/// @param feedsBloom The bloom filter encoding the feeds to update their
/// config's url and content hash.
/// @param url The url of the new config.
/// @param contentHash The keccak256 hash of the config's content.
function setConfig(
uint feedsBloom,
string calldata url,
bytes32 contentHash
) external;
/// @notice Removes the config for feeds encoded in bloom filter
/// `feedsBloom`.
///
/// @dev Only callable by auth'ed addresses.
///
/// @param feedsBloom The bloom filter encoding the feeds to remove their
/// config.
function unsetConfig(uint feedsBloom) external;
}
{
"compilationTarget": {
"src/ConfigRegistry.sol": "ConfigRegistry_1"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 10000
},
"remappings": [
":@script/chronicle-std/=lib/chronicle-std/script/",
":chronicle-std/=lib/chronicle-std/src/",
":ds-test/=lib/forge-std/lib/ds-test/src/",
":forge-std/=lib/forge-std/src/",
":greenhouse/=lib/greenhouse/src/"
],
"viaIR": true
}
[{"inputs":[{"internalType":"address","name":"initialAuthed","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint8","name":"feedId","type":"uint8"}],"name":"ConfigDoesNotExist","type":"error"},{"inputs":[{"internalType":"address","name":"caller","type":"address"}],"name":"NotAuthorized","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":true,"internalType":"address","name":"who","type":"address"}],"name":"AuthGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":true,"internalType":"address","name":"who","type":"address"}],"name":"AuthRenounced","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":true,"internalType":"uint256","name":"feedsBloom","type":"uint256"}],"name":"ConfigRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":true,"internalType":"uint256","name":"feedsBloom","type":"uint256"}],"name":"ConfigUpdated","type":"event"},{"inputs":[{"internalType":"address","name":"who","type":"address"}],"name":"authed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"authed","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"feed","type":"address"}],"name":"configs","outputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"who","type":"address"}],"name":"deny","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"who","type":"address"}],"name":"rely","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"feedsBloom","type":"uint256"},{"internalType":"string","name":"url","type":"string"},{"internalType":"bytes32","name":"contentHash","type":"bytes32"}],"name":"setConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"feedsBloom","type":"uint256"}],"name":"unsetConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"who","type":"address"}],"name":"wards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]