Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement CCIPHome based on RMNHome #1459

Draft
wants to merge 36 commits into
base: ccip-develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
b250524
init rmn home
RensR Sep 17, 2024
a2633a3
refactor rmn home
RensR Sep 18, 2024
606981c
add dynamic/static
RensR Sep 19, 2024
36720d2
add more tests
RensR Sep 19, 2024
21e8308
rm promoteSecondary
RensR Sep 23, 2024
053aa99
store bytes instead of struct
RensR Sep 23, 2024
e5814d0
extract HomeBase
RensR Sep 23, 2024
3992dac
extract digest calc
RensR Sep 23, 2024
7ddbf72
add version to storedConfig
RensR Sep 23, 2024
451a2fc
add getters for configs in base
RensR Sep 23, 2024
3647d25
have getters use base getters
RensR Sep 23, 2024
97c1471
extract validation functions
RensR Sep 23, 2024
32a3cda
move all setters to HomeBase
RensR Sep 23, 2024
0b33d7a
add donId and pluginType to HomeBase
RensR Sep 23, 2024
35e56e5
add CR support to RMN
RensR Sep 23, 2024
725a3d9
add ccipHome
RensR Sep 23, 2024
0c07613
fix getter types
RensR Sep 23, 2024
f1766b2
fix tests & make all config methods callable
RensR Sep 23, 2024
72b0884
add getAllChainConfigs
RensR Sep 23, 2024
4318f80
add don and type to getAllConfig
RensR Sep 23, 2024
ed271dc
add DON id and plugin type to getConfig
RensR Sep 23, 2024
d572c5c
add basic tests
RensR Sep 23, 2024
488c5e6
use single key to index
RensR Sep 23, 2024
5309aa2
move CR to CCIPHome
RensR Sep 23, 2024
ce86ed6
extract caller validation into implementations
RensR Sep 24, 2024
0b64028
improve comments, rm ccipConfig
RensR Sep 24, 2024
2d7ed93
put base into CCIPHome
RensR Sep 25, 2024
fff5d77
ccipHome without inheritance
RensR Sep 25, 2024
68b96a7
rm pluginKey in rmn
RensR Sep 25, 2024
a445b38
use correct types in RMNHome
RensR Sep 25, 2024
17b57ab
cleanup
RensR Sep 25, 2024
57f63e3
add storing of ccip config
RensR Sep 25, 2024
d309e11
fix typos
RensR Sep 26, 2024
3854ec9
rename blue/green/primary/secondary -> active/candidate
RensR Sep 26, 2024
9426e0c
improve comments
RensR Sep 26, 2024
60d07b4
full cov rmn
RensR Sep 26, 2024
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
26 changes: 26 additions & 0 deletions contracts/gas-snapshots/ccip.gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -809,6 +809,32 @@ PingPong_plumbing:test_OutOfOrderExecution_Success() (gas: 20310)
PingPong_plumbing:test_Pausing_Success() (gas: 17810)
PingPong_startPingPong:test_StartPingPong_With_OOO_Success() (gas: 162091)
PingPong_startPingPong:test_StartPingPong_With_Sequenced_Ordered_Success() (gas: 181509)
RMNHome_promoteSecondaryAndRevokePrimary:test_promoteSecondaryAndRevokePrimary_OnlyOwner_reverts() (gas: 10885)
RMNHome_promoteSecondaryAndRevokePrimary:test_promoteSecondaryAndRevokePrimary_OnlyOwner_reverts() (gas: 10997)
RMNHome_promoteSecondaryAndRevokePrimary:test_promoteSecondaryAndRevokePrimary_success() (gas: 209)
RMNHome_promoteSecondaryAndRevokePrimary:test_promoteSecondaryAndRevokePrimary_success() (gas: 209)
RMNHome_revokeSecondary:test_revokeSecondary_ConfigDigestMismatch_reverts() (gas: 19344)
RMNHome_revokeSecondary:test_revokeSecondary_ConfigDigestMismatch_reverts() (gas: 19344)
RMNHome_revokeSecondary:test_revokeSecondary_OnlyOwner_reverts() (gas: 10890)
RMNHome_revokeSecondary:test_revokeSecondary_OnlyOwner_reverts() (gas: 10912)
RMNHome_revokeSecondary:test_revokeSecondary_success() (gas: 27088)
RMNHome_revokeSecondary:test_revokeSecondary_success() (gas: 28943)
RMNHome_setDynamicConfig:test_setDynamicConfig_DigestNotFound_reverts() (gas: 33356)
RMNHome_setDynamicConfig:test_setDynamicConfig_MinObserversTooHigh_reverts() (gas: 20467)
RMNHome_setDynamicConfig:test_setDynamicConfig_OnlyOwner_reverts() (gas: 11812)
RMNHome_setDynamicConfig:test_setDynamicConfig_OnlyOwner_reverts() (gas: 15502)
RMNHome_setDynamicConfig:test_setDynamicConfig_success() (gas: 133259)
RMNHome_setDynamicConfig:test_setDynamicConfig_success() (gas: 63157)
RMNHome_setSecondary:test_setSecondary_DuplicateOffchainPublicKey_reverts() (gas: 20872)
RMNHome_setSecondary:test_setSecondary_DuplicatePeerId_reverts() (gas: 20666)
RMNHome_setSecondary:test_setSecondary_DuplicateSourceChain_reverts() (gas: 24457)
RMNHome_setSecondary:test_setSecondary_MinObserversTooHigh_reverts() (gas: 24896)
RMNHome_setSecondary:test_setSecondary_OnlyOwner_reverts() (gas: 12661)
RMNHome_setSecondary:test_setSecondary_OnlyOwner_reverts() (gas: 18048)
RMNHome_setSecondary:test_setSecondary_OutOfBoundsNodesLength_reverts() (gas: 186804)
RMNHome_setSecondary:test_setSecondary_OutOfBoundsObserverNodeIndex_reverts() (gas: 24601)
RMNHome_setSecondary:test_setSecondary_success() (gas: 282455)
RMNHome_setSecondary:test_setSecondary_success() (gas: 820695)
RMNRemote_constructor:test_constructor_success() (gas: 8334)
RMNRemote_constructor:test_constructor_zeroChainSelector_reverts() (gas: 59165)
RMNRemote_curse:test_curse_AlreadyCursed_duplicateSubject_reverts() (gas: 154457)
Expand Down
362 changes: 362 additions & 0 deletions contracts/src/v0.8/ccip/capability/CCIPHome.sol

Large diffs are not rendered by default.

236 changes: 236 additions & 0 deletions contracts/src/v0.8/ccip/capability/HomeBase.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.24;

import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol";

import {OwnerIsCreator} from "../../shared/access/OwnerIsCreator.sol";

abstract contract HomeBase is OwnerIsCreator, ITypeAndVersion {
event ConfigSet(StoredConfig versionedConfig);
event ConfigRevoked(bytes32 indexed configDigest);
event DynamicConfigSet(bytes32 indexed configDigest, bytes dynamicConfig);
event ConfigPromoted(bytes32 indexed configDigest);

error ConfigDigestMismatch(bytes32 expectedConfigDigest, bytes32 gotConfigDigest);
error DigestNotFound(bytes32 configDigest);
error ZeroAddressNotAllowed();
error OnlyCapabilitiesRegistryCanCall();
error OnlyOwnerOrSelfCallAllowed();

/// @notice Used for encoding the config digest prefix
uint256 private constant PREFIX_MASK = type(uint256).max << (256 - 16); // 0xFFFF00..00
/// @notice The max number of configs that can be active at the same time.
uint256 private constant MAX_CONCURRENT_CONFIGS = 2;
/// @notice Helper to identify the zero config digest with less casting.
bytes32 private constant ZERO_DIGEST = bytes32(uint256(0));

/// @notice This array holds the configs.
/// @dev Value i in this array is valid iff s_configs[i].configDigest != 0.
mapping(bytes32 pluginKey => StoredConfig[MAX_CONCURRENT_CONFIGS]) private s_configs;

/// @notice The total number of configs ever set, used for generating the version of the configs.
uint32 private s_configCount = 0;
/// @notice The index of the primary config.
uint32 private s_primaryConfigIndex = 0;

struct StoredConfig {
bytes32 configDigest;
uint32 version;
bytes staticConfig;
bytes dynamicConfig;
}

function _validateStaticAndDynamicConfig(bytes memory staticConfig, bytes memory dynamicConfig) internal view virtual;

function _validateDynamicConfig(bytes memory staticConfig, bytes memory dynamicConfig) internal view virtual;

function _getConfigDigestPrefix() internal pure virtual returns (uint256);

// ================================================================
// │ Getters │
// ================================================================

/// @notice Returns the current primary and secondary config digests.
/// @dev Can be bytes32(0) if no config has been set yet or it has been revoked.
/// @return primaryConfigDigest The digest of the primary config.
/// @return secondaryConfigDigest The digest of the secondary config.
function getConfigDigests(
bytes32 pluginKey
) external view returns (bytes32 primaryConfigDigest, bytes32 secondaryConfigDigest) {
return (
s_configs[pluginKey][s_primaryConfigIndex].configDigest,
s_configs[pluginKey][s_primaryConfigIndex ^ 1].configDigest
);
}

function getPrimaryDigest(bytes32 pluginKey) public view returns (bytes32) {
return s_configs[pluginKey][s_primaryConfigIndex].configDigest;
}

function getSecondaryDigest(bytes32 pluginKey) public view returns (bytes32) {
return s_configs[pluginKey][s_primaryConfigIndex ^ 1].configDigest;
}

/// @notice Returns the stored config for a given digest. Will always return an empty config if the digest is the zero
/// digest. This is done to prevent exposing old config state that is invalid.
function _getStoredConfig(
bytes32 pluginKey,
RensR marked this conversation as resolved.
Show resolved Hide resolved
bytes32 configDigest
) internal view returns (StoredConfig memory storedConfig, bool ok) {
for (uint256 i = 0; i < MAX_CONCURRENT_CONFIGS; ++i) {
// We never want to return true for a zero digest, even if the caller is asking for it, as this can expose old
// config state that is invalid.
if (s_configs[pluginKey][i].configDigest == configDigest && configDigest != ZERO_DIGEST) {
return (s_configs[pluginKey][i], true);
}
}
return (storedConfig, false);
RensR marked this conversation as resolved.
Show resolved Hide resolved
}

function _getPrimaryStoredConfig(
bytes32 pluginKey
) internal view returns (StoredConfig memory primaryConfig, bool ok) {
if (s_configs[pluginKey][s_primaryConfigIndex].configDigest == ZERO_DIGEST) {
return (StoredConfig(ZERO_DIGEST, 0, "", ""), false);
}

return (s_configs[pluginKey][s_primaryConfigIndex], true);
}

function _getSecondaryStoredConfig(
bytes32 pluginKey
) internal view returns (StoredConfig memory secondaryConfig, bool ok) {
if (s_configs[pluginKey][s_primaryConfigIndex ^ 1].configDigest == ZERO_DIGEST) {
return (StoredConfig(ZERO_DIGEST, 0, "", ""), false);
}

return (s_configs[pluginKey][s_primaryConfigIndex ^ 1], true);
}

// ================================================================
// │ State transitions │
// ================================================================

/// @notice Sets a new config as the secondary config. Does not influence the primary config.
/// @param digestToOverwrite The digest of the config to overwrite, or ZERO_DIGEST if no config is to be overwritten.
/// This is done to prevent accidental overwrites.
function setSecondary(
bytes32 pluginKey,
bytes calldata encodedStaticConfig,
bytes calldata encodedDynamicConfig,
bytes32 digestToOverwrite
) external OnlyOwnerOrSelfCall returns (bytes32 newConfigDigest) {
_validateStaticAndDynamicConfig(encodedStaticConfig, encodedDynamicConfig);

bytes32 existingDigest = getSecondaryDigest(pluginKey);

if (existingDigest != digestToOverwrite) {
revert ConfigDigestMismatch(existingDigest, digestToOverwrite);
}

// are we going to overwrite a config? If so, emit an event.
if (existingDigest != ZERO_DIGEST) {
emit ConfigRevoked(digestToOverwrite);
}

uint32 newVersion = ++s_configCount;
newConfigDigest = _calculateConfigDigest(pluginKey, encodedStaticConfig, newVersion);

StoredConfig memory newConfig = StoredConfig({
configDigest: newConfigDigest,
version: newVersion,
staticConfig: encodedStaticConfig,
dynamicConfig: encodedDynamicConfig
});

s_configs[pluginKey][s_primaryConfigIndex ^ 1] = newConfig;

emit ConfigSet(newConfig);

return newConfigDigest;
}

/// @notice Revokes a specific config by digest.
/// @param configDigest The digest of the config to revoke. This is done to prevent accidental revokes.
function revokeSecondary(bytes32 pluginKey, bytes32 configDigest) external OnlyOwnerOrSelfCall {
uint256 secondaryConfigIndex = s_primaryConfigIndex ^ 1;
if (s_configs[pluginKey][secondaryConfigIndex].configDigest != configDigest) {
revert ConfigDigestMismatch(s_configs[pluginKey][secondaryConfigIndex].configDigest, configDigest);
}

emit ConfigRevoked(configDigest);
// Delete only the digest, as that's what's used to determine if a config is active. This means the actual
// config stays in storage which should significantly reduce the gas cost of overwriting that storage space in
// the future.
delete s_configs[pluginKey][secondaryConfigIndex].configDigest;
}

/// @notice Promotes the secondary config to the primary config and revokes the primary config.
function promoteSecondaryAndRevokePrimary(
bytes32 pluginKey,
bytes32 digestToPromote,
bytes32 digestToRevoke
) external OnlyOwnerOrSelfCall {
uint256 secondaryConfigIndex = s_primaryConfigIndex ^ 1;
if (s_configs[pluginKey][secondaryConfigIndex].configDigest != digestToPromote) {
revert ConfigDigestMismatch(s_configs[pluginKey][secondaryConfigIndex].configDigest, digestToPromote);
}

uint256 primaryConfigIndex = s_primaryConfigIndex;
if (s_configs[pluginKey][primaryConfigIndex].configDigest != digestToRevoke) {
revert ConfigDigestMismatch(s_configs[pluginKey][primaryConfigIndex].configDigest, digestToRevoke);
}

delete s_configs[pluginKey][primaryConfigIndex].configDigest;

s_primaryConfigIndex ^= 1;
if (digestToRevoke != ZERO_DIGEST) {
emit ConfigRevoked(digestToRevoke);
}
emit ConfigPromoted(digestToPromote);
}

function setDynamicConfig(
bytes32 pluginKey,
bytes calldata newDynamicConfig,
bytes32 currentDigest
) external OnlyOwnerOrSelfCall {
for (uint256 i = 0; i < MAX_CONCURRENT_CONFIGS; ++i) {
if (s_configs[pluginKey][i].configDigest == currentDigest && currentDigest != ZERO_DIGEST) {
_validateDynamicConfig(s_configs[pluginKey][i].staticConfig, newDynamicConfig);

// Since the static config doesn't change we don't have to update the digest or version.
s_configs[pluginKey][i].dynamicConfig = newDynamicConfig;

emit DynamicConfigSet(currentDigest, newDynamicConfig);
return;
}
}

revert DigestNotFound(currentDigest);
}

function _calculateConfigDigest(
bytes32 pluginKey,
bytes memory staticConfig,
uint32 version
) internal view returns (bytes32) {
return bytes32(
(_getConfigDigestPrefix() & PREFIX_MASK)
| (
uint256(
keccak256(
bytes.concat(abi.encode(bytes32("EVM"), block.chainid, address(this), pluginKey, version), staticConfig)
)
) & ~PREFIX_MASK
)
);
}

modifier OnlyOwnerOrSelfCall() {
if (msg.sender != owner() && msg.sender != address(this)) {
revert OnlyOwnerOrSelfCallAllowed();
}
_;
}
RensR marked this conversation as resolved.
Show resolved Hide resolved
}
Loading
Loading