Skip to content

Commit

Permalink
Fix multichain keybundle (#14369)
Browse files Browse the repository at this point in the history
* Fix multichain adapter

* Use more compact binary encoding for multi-chain public key encoding

* keystone: Replace transmit function

Overriding _report doesn't actually provide a clear error since
transmissions will fail much earlier in the transmit function.

Replacing this function should also make this contract cheaper to deploy
since we're not deploying unused code.

* keystone: Remove unused beforeSetConfig hook

* keystone: OCR3Capability to store all pubkeys for multi-chain bundles

* keystone: Use custom decoder for OCR3Capability

* scripts: keystone: Use the new encoding format for pubkeys

* keystone: Merge OCR2Base into OCR3Capability since we already diverged

* keystone: Fix setConfig typing so typechain bindings actually generate

* Use a custom digester and log decoder

* Use multi-chain setup for signing (single-chain won't work anymore)

* Remove stray aptos key bundle

* Update test snapshot

* remove stray aptos code

* solhint

* Add clause for OCR3 capability

* bootstrap: Only setup the config provider, not the whole plugin

* address lints

* Update test snapshot

* check for overflow

* Use a different config digest prefix for OCR3Capability

* keystone: Remove s_signers, it's unused since we only emit event

* keystone: Remove SetConfigArgs

* Update gethwrappers

* Update config digest prefix to latest agreed upon value

* keystone: Validate uniqueness of all keys inside the multi-chain key list

* Update gethwrappers

* Fix pruning query

---------

Co-authored-by: Cedric Cordenier <cedric.cordenier@smartcontract.com>
Co-authored-by: app-token-issuer-infra-releng[bot] <120227048+app-token-issuer-infra-releng[bot]@users.noreply.github.com>
Co-authored-by: Domino Valdano <2644901+reductionista@users.noreply.github.com>
  • Loading branch information
4 people authored Sep 17, 2024
1 parent 37c5a2f commit e514727
Show file tree
Hide file tree
Showing 18 changed files with 732 additions and 481 deletions.
5 changes: 5 additions & 0 deletions .changeset/red-icons-build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink": minor
---

Small fixes to multichain keyring adapter #internal
185 changes: 171 additions & 14 deletions contracts/src/v0.8/keystone/OCR3Capability.sol
Original file line number Diff line number Diff line change
@@ -1,29 +1,186 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import {OCR2Base} from "./ocr/OCR2Base.sol";
import {ConfirmedOwner} from "../shared/access/ConfirmedOwner.sol";
import {OCR2Abstract} from "./ocr/OCR2Abstract.sol";

// OCR2Base provides config management compatible with OCR3
contract OCR3Capability is OCR2Base {
contract OCR3Capability is ConfirmedOwner, OCR2Abstract {
error InvalidConfig(string message);
error ReportingUnsupported();

constructor() OCR2Base() {}
constructor() ConfirmedOwner(msg.sender) {}
// incremented each time a new config is posted. This count is incorporated
// into the config digest, to prevent replay attacks.
uint32 internal s_configCount;
uint32 internal s_latestConfigBlockNumber; // makes it easier for offchain systems
// to extract config from logs.

function typeAndVersion() external pure override returns (string memory) {
return "Keystone 1.0.0";
// Storing these fields used on the hot path in a ConfigInfo variable reduces the
// retrieval of all of them to a single SLOAD. If any further fields are
// added, make sure that storage of the struct still takes at most 32 bytes.
struct ConfigInfo {
bytes32 latestConfigDigest;
uint8 f; // TODO: could be optimized by squeezing into one slot
uint8 n;
}
ConfigInfo internal s_configInfo;

/*
* Config logic
*/

// Reverts transaction if config args are invalid
modifier checkConfigValid(
uint256 numSigners,
uint256 numTransmitters,
uint256 f
) {
if (numSigners > MAX_NUM_ORACLES) revert InvalidConfig("too many signers");
if (f == 0) revert InvalidConfig("f must be positive");
if (numSigners != numTransmitters) revert InvalidConfig("oracle addresses out of registration");
if (numSigners <= 3 * f) revert InvalidConfig("faulty-oracle f too high");
_;
}

function _beforeSetConfig(uint8 /* _f */, bytes memory /* _onchainConfig */) internal override {
// no-op
/// @inheritdoc OCR2Abstract
function latestConfigDigestAndEpoch()
external
view
virtual
override
returns (bool scanLogs, bytes32 configDigest, uint32 epoch)
{
return (true, bytes32(0), uint32(0));
}

// signer = [ 1 byte type | 2 byte len | n byte value ]...

/**
* @notice sets offchain reporting protocol configuration incl. participating oracles
* @param _signers addresses with which oracles sign the reports
* @param _transmitters addresses oracles use to transmit the reports
* @param _f number of faulty oracles the system can tolerate
* @param _onchainConfig encoded on-chain contract configuration
* @param _offchainConfigVersion version number for offchainEncoding schema
* @param _offchainConfig encoded off-chain oracle configuration
*/
function setConfig(
bytes[] calldata _signers,
address[] calldata _transmitters,
uint8 _f,
bytes memory _onchainConfig,
uint64 _offchainConfigVersion,
bytes memory _offchainConfig
) external override checkConfigValid(_signers.length, _transmitters.length, _f) onlyOwner {
// Bounded by MAX_NUM_ORACLES in OCR2Abstract.sol
for (uint256 i = 0; i < _signers.length; i++) {
if (_transmitters[i] == address(0)) revert InvalidConfig("transmitter must not be empty");
// add new signers
bytes calldata publicKeys = _signers[i];
uint16 offset = 0;
uint16 len = uint16(publicKeys.length);
// scan through public keys to validate encoded format
while (offset < len) {
// solhint-disable-next-line no-unused-vars
uint8 keyType = uint8(publicKeys[offset]);
uint16 keyLen = uint16(uint8(publicKeys[offset + 1])) + (uint16(uint8(publicKeys[offset + 2])) << 8);
// solhint-disable-next-line no-unused-vars
bytes calldata publicKey = publicKeys[offset + 3:offset + 3 + keyLen];
offset += 3 + keyLen;
}
}
s_configInfo.f = _f;
uint32 previousConfigBlockNumber = s_latestConfigBlockNumber;
s_latestConfigBlockNumber = uint32(block.number);
s_configCount += 1;
{
s_configInfo.latestConfigDigest = _configDigestFromConfigData(
block.chainid,
address(this),
s_configCount,
_signers,
_transmitters,
_f,
_onchainConfig,
_offchainConfigVersion,
_offchainConfig
);
}
s_configInfo.n = uint8(_signers.length);

emit ConfigSet(
previousConfigBlockNumber,
s_configInfo.latestConfigDigest,
s_configCount,
_signers,
_transmitters,
_f,
_onchainConfig,
_offchainConfigVersion,
_offchainConfig
);
}

function _configDigestFromConfigData(
uint256 _chainId,
address _contractAddress,
uint64 _configCount,
bytes[] calldata _signers,
address[] calldata _transmitters,
uint8 _f,
bytes memory _onchainConfig,
uint64 _encodedConfigVersion,
bytes memory _encodedConfig
) internal pure returns (bytes32) {
uint256 h = uint256(
keccak256(
abi.encode(
_chainId,
_contractAddress,
_configCount,
_signers,
_transmitters,
_f,
_onchainConfig,
_encodedConfigVersion,
_encodedConfig
)
)
);
uint256 prefixMask = type(uint256).max << (256 - 16); // 0xFFFF00..00
uint256 prefix = 0x000e << (256 - 16); // 0x000e00..00
return bytes32((prefix & prefixMask) | (h & ~prefixMask));
}

/**
* @notice information about current offchain reporting protocol configuration
* @return configCount ordinal number of current config, out of all configs applied to this contract so far
* @return blockNumber block at which this config was set
* @return configDigest domain-separation tag for current config (see __configDigestFromConfigData)
*/
function latestConfigDetails()
external
view
override
returns (uint32 configCount, uint32 blockNumber, bytes32 configDigest)
{
return (s_configCount, s_latestConfigBlockNumber, s_configInfo.latestConfigDigest);
}

function typeAndVersion() external pure override returns (string memory) {
return "Keystone 1.0.0";
}

function _report(
uint256 /* initialGas */,
address /* transmitter */,
uint8 /* signerCount */,
address[MAX_NUM_ORACLES] memory /* signers */,
bytes calldata /* report */
) internal virtual override {
function transmit(
// NOTE: If these parameters are changed, expectedMsgDataLength and/or
// TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT need to be changed accordingly
bytes32[3] calldata /* reportContext */,
bytes calldata /* report */,
bytes32[] calldata /* rs */,
bytes32[] calldata /* ss */,
bytes32 /* rawVs */ // signatures
) external pure override {
revert ReportingUnsupported();
}
}
4 changes: 2 additions & 2 deletions contracts/src/v0.8/keystone/ocr/OCR2Abstract.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ abstract contract OCR2Abstract is ITypeAndVersion {
uint32 previousConfigBlockNumber,
bytes32 configDigest,
uint64 configCount,
address[] signers,
bytes[] signers,
address[] transmitters,
uint8 f,
bytes onchainConfig,
Expand All @@ -41,7 +41,7 @@ abstract contract OCR2Abstract is ITypeAndVersion {
* @param offchainConfig serialized configuration used by the oracles exclusively and only passed through the contract
*/
function setConfig(
address[] memory signers,
bytes[] memory signers,
address[] memory transmitters,
uint8 f,
bytes memory onchainConfig,
Expand Down
Loading

0 comments on commit e514727

Please sign in to comment.