Skip to content

Commit

Permalink
add basic tests
Browse files Browse the repository at this point in the history
  • Loading branch information
RensR committed Sep 23, 2024
1 parent ed271dc commit d572c5c
Show file tree
Hide file tree
Showing 4 changed files with 335 additions and 60 deletions.
47 changes: 29 additions & 18 deletions contracts/gas-snapshots/ccip.gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -809,25 +809,36 @@ 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_beforeCapabilityConfigSet:test_beforeCapabilityConfigSet_OnlyCapabilitiesRegistryCanCall_reverts() (gas: 9867)
RMNHome_beforeCapabilityConfigSet:test_beforeCapabilityConfigSet_success() (gas: 187)
RMNHome_promoteSecondaryAndRevokePrimary:test_promoteSecondaryAndRevokePrimary_OnlyOwner_reverts() (gas: 11121)
RMNHome_beforeCapabilityConfigSet:test_beforeCapabilityConfigSet_OnlyCapabilitiesRegistryCanCall_reverts() (gas: 9872)
RMNHome_beforeCapabilityConfigSet:test_beforeCapabilityConfigSet_OnlyCapabilitiesRegistryCanCall_reverts() (gas: 9901)
RMNHome_beforeCapabilityConfigSet:test_beforeCapabilityConfigSet_success() (gas: 275772)
RMNHome_beforeCapabilityConfigSet:test_beforeCapabilityConfigSet_success() (gas: 794676)
RMNHome_promoteSecondaryAndRevokePrimary:test_promoteSecondaryAndRevokePrimary_OnlyOwner_reverts() (gas: 11073)
RMNHome_promoteSecondaryAndRevokePrimary:test_promoteSecondaryAndRevokePrimary_OnlyOwner_reverts() (gas: 11119)
RMNHome_promoteSecondaryAndRevokePrimary:test_promoteSecondaryAndRevokePrimary_success() (gas: 209)
RMNHome_revokeSecondary:test_revokeSecondary_ConfigDigestMismatch_reverts() (gas: 20027)
RMNHome_revokeSecondary:test_revokeSecondary_OnlyOwner_reverts() (gas: 11177)
RMNHome_revokeSecondary:test_revokeSecondary_success() (gas: 30125)
RMNHome_setDynamicConfig:test_setDynamicConfig_DigestNotFound_reverts() (gas: 34017)
RMNHome_setDynamicConfig:test_setDynamicConfig_MinObserversTooHigh_reverts() (gas: 20877)
RMNHome_setDynamicConfig:test_setDynamicConfig_OnlyOwner_reverts() (gas: 15831)
RMNHome_setDynamicConfig:test_setDynamicConfig_success() (gas: 135045)
RMNHome_setSecondary:test_setSecondary_DuplicateOffchainPublicKey_reverts() (gas: 21070)
RMNHome_setSecondary:test_setSecondary_DuplicatePeerId_reverts() (gas: 20864)
RMNHome_setSecondary:test_setSecondary_DuplicateSourceChain_reverts() (gas: 24663)
RMNHome_setSecondary:test_setSecondary_MinObserversTooHigh_reverts() (gas: 25102)
RMNHome_setSecondary:test_setSecondary_OnlyOwner_reverts() (gas: 18283)
RMNHome_setSecondary:test_setSecondary_OutOfBoundsNodesLength_reverts() (gas: 187009)
RMNHome_setSecondary:test_setSecondary_OutOfBoundsObserverNodeIndex_reverts() (gas: 24807)
RMNHome_setSecondary:test_setSecondary_success() (gas: 821271)
RMNHome_promoteSecondaryAndRevokePrimary:test_promoteSecondaryAndRevokePrimary_success() (gas: 209)
RMNHome_revokeSecondary:test_revokeSecondary_ConfigDigestMismatch_reverts() (gas: 20060)
RMNHome_revokeSecondary:test_revokeSecondary_ConfigDigestMismatch_reverts() (gas: 20108)
RMNHome_revokeSecondary:test_revokeSecondary_OnlyOwner_reverts() (gas: 11085)
RMNHome_revokeSecondary:test_revokeSecondary_OnlyOwner_reverts() (gas: 11129)
RMNHome_revokeSecondary:test_revokeSecondary_success() (gas: 28514)
RMNHome_revokeSecondary:test_revokeSecondary_success() (gas: 30361)
RMNHome_setDynamicConfig:test_setDynamicConfig_DigestNotFound_reverts() (gas: 34065)
RMNHome_setDynamicConfig:test_setDynamicConfig_MinObserversTooHigh_reverts() (gas: 20901)
RMNHome_setDynamicConfig:test_setDynamicConfig_OnlyOwner_reverts() (gas: 11973)
RMNHome_setDynamicConfig:test_setDynamicConfig_OnlyOwner_reverts() (gas: 15786)
RMNHome_setDynamicConfig:test_setDynamicConfig_success() (gas: 135276)
RMNHome_setDynamicConfig:test_setDynamicConfig_success() (gas: 64750)
RMNHome_setSecondary:test_setSecondary_DuplicateOffchainPublicKey_reverts() (gas: 21062)
RMNHome_setSecondary:test_setSecondary_DuplicatePeerId_reverts() (gas: 20856)
RMNHome_setSecondary:test_setSecondary_DuplicateSourceChain_reverts() (gas: 24623)
RMNHome_setSecondary:test_setSecondary_MinObserversTooHigh_reverts() (gas: 25062)
RMNHome_setSecondary:test_setSecondary_OnlyOwner_reverts() (gas: 12873)
RMNHome_setSecondary:test_setSecondary_OnlyOwner_reverts() (gas: 18238)
RMNHome_setSecondary:test_setSecondary_OutOfBoundsNodesLength_reverts() (gas: 187001)
RMNHome_setSecondary:test_setSecondary_OutOfBoundsObserverNodeIndex_reverts() (gas: 24767)
RMNHome_setSecondary:test_setSecondary_success() (gas: 283462)
RMNHome_setSecondary:test_setSecondary_success() (gas: 821737)
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
96 changes: 54 additions & 42 deletions contracts/src/v0.8/ccip/capability/HomeBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ abstract contract HomeBase is OwnerIsCreator, ITypeAndVersion, ICapabilityConfig

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

// ================================================================
// │ Capability Registry │
// ================================================================

/// @notice Returns the capabilities registry address.
/// @return The capabilities registry address.
function getCapabilityRegistry() external view returns (address) {
Expand Down Expand Up @@ -95,6 +99,32 @@ abstract contract HomeBase is OwnerIsCreator, ITypeAndVersion, ICapabilityConfig
return bytes("");
}

// ================================================================
// │ 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(
uint32 donId,
uint8 pluginType
) external view returns (bytes32 primaryConfigDigest, bytes32 secondaryConfigDigest) {
return (
s_configs[donId][pluginType][s_primaryConfigIndex].configDigest,
s_configs[donId][pluginType][s_primaryConfigIndex ^ 1].configDigest
);
}

function getPrimaryDigest(uint32 donId, uint8 pluginType) public view returns (bytes32) {
return s_configs[donId][pluginType][s_primaryConfigIndex].configDigest;
}

function getSecondaryDigest(uint32 donId, uint8 pluginType) public view returns (bytes32) {
return s_configs[donId][pluginType][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(
Expand Down Expand Up @@ -134,27 +164,9 @@ abstract contract HomeBase is OwnerIsCreator, ITypeAndVersion, ICapabilityConfig
return (s_configs[donId][pluginType][s_primaryConfigIndex ^ 1], true);
}

function getPrimaryDigest(uint32 donId, uint8 pluginType) public view returns (bytes32) {
return s_configs[donId][pluginType][s_primaryConfigIndex].configDigest;
}

function getSecondaryDigest(uint32 donId, uint8 pluginType) public view returns (bytes32) {
return s_configs[donId][pluginType][s_primaryConfigIndex ^ 1].configDigest;
}

/// @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(
uint32 donId,
uint8 pluginType
) external view returns (bytes32 primaryConfigDigest, bytes32 secondaryConfigDigest) {
return (
s_configs[donId][pluginType][s_primaryConfigIndex].configDigest,
s_configs[donId][pluginType][s_primaryConfigIndex ^ 1].configDigest
);
}
// ================================================================
// │ 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.
Expand Down Expand Up @@ -196,27 +208,6 @@ abstract contract HomeBase is OwnerIsCreator, ITypeAndVersion, ICapabilityConfig
return newConfigDigest;
}

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

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

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

revert DigestNotFound(currentDigest);
}

/// @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(uint32 donId, uint8 pluginType, bytes32 configDigest) external OnlyOwnerOrSelfCall {
Expand Down Expand Up @@ -258,6 +249,27 @@ abstract contract HomeBase is OwnerIsCreator, ITypeAndVersion, ICapabilityConfig
emit ConfigPromoted(digestToPromote);
}

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

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

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

revert DigestNotFound(currentDigest);
}

function _calculateConfigDigest(
uint32 donId,
uint8 pluginType,
Expand Down
204 changes: 204 additions & 0 deletions contracts/src/v0.8/ccip/test/capability/HomeBaseTest.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.24;

import {HomeBase} from "../../capability/HomeBase.sol";
import {Internal} from "../../libraries/Internal.sol";
import {HomeBaseHelper} from "../helpers/HomeBaseHelper.sol";
import {Test} from "forge-std/Test.sol";
import {Vm} from "forge-std/Vm.sol";

contract HomeBaseTest is Test {
uint32 internal constant DON_ID = 593;
uint8 internal constant PLUGIN_TYPE = 244;

bytes32 internal constant ZERO_DIGEST = bytes32(uint256(0));

HomeBaseHelper internal s_homeBase;
address private constant CAPABILITIES_REGISTRY = address(1);

function setUp() public virtual {
s_homeBase = new HomeBaseHelper(CAPABILITIES_REGISTRY);
}

uint256 private constant PREFIX_MASK = type(uint256).max << (256 - 16); // 0xFFFF00..00
uint256 public constant PREFIX = 0x0c0c << (256 - 16);

function _getConfigDigest(bytes memory staticConfig, uint32 version) internal view returns (bytes32) {
return bytes32(
(PREFIX & PREFIX_MASK)
| (
uint256(
keccak256(
bytes.concat(
abi.encode(bytes32("EVM"), block.chainid, address(s_homeBase), DON_ID, PLUGIN_TYPE, version), staticConfig
)
)
) & ~PREFIX_MASK
)
);
}

function _getStaticConfig() internal pure returns (bytes memory) {
return abi.encode("staticConfig");
}

function _getDynamicConfig() internal pure returns (bytes memory) {
return abi.encode("dynamicConfig");
}
}

contract RMNHome_setSecondary is HomeBaseTest {
function test_setSecondary_success() public {
HomeBase.StoredConfig memory encodedConfig = HomeBase.StoredConfig({
configDigest: ZERO_DIGEST,
version: 1,
staticConfig: _getStaticConfig(),
dynamicConfig: _getDynamicConfig()
});

encodedConfig.configDigest = _getConfigDigest(encodedConfig.staticConfig, encodedConfig.version);

vm.expectEmit();
emit HomeBase.ConfigSet(encodedConfig);

s_homeBase.setSecondary(DON_ID, PLUGIN_TYPE, encodedConfig.staticConfig, encodedConfig.dynamicConfig, ZERO_DIGEST);

(HomeBase.StoredConfig memory storedConfig, bool ok) = s_homeBase.getSecondaryStoredConfig(DON_ID, PLUGIN_TYPE);
assertTrue(ok);
assertEq(storedConfig.version, encodedConfig.version);
assertEq(storedConfig.configDigest, encodedConfig.configDigest);
assertEq(storedConfig.staticConfig, encodedConfig.staticConfig);
assertEq(storedConfig.dynamicConfig, encodedConfig.dynamicConfig);
}

function test_setSecondary_OnlyOwner_reverts() public {
vm.startPrank(address(0));

vm.expectRevert(HomeBase.OnlyOwnerOrSelfCallAllowed.selector);
s_homeBase.setSecondary(DON_ID, PLUGIN_TYPE, _getStaticConfig(), _getDynamicConfig(), ZERO_DIGEST);
}
}

contract RMNHome_setDynamicConfig is HomeBaseTest {
function setUp() public override {
super.setUp();
s_homeBase.setSecondary(DON_ID, PLUGIN_TYPE, _getStaticConfig(), _getDynamicConfig(), ZERO_DIGEST);
}

function test_setDynamicConfig_success() public {
(bytes32 priorPrimaryDigest, bytes32 secondaryConfigDigest) = s_homeBase.getConfigDigests(DON_ID, PLUGIN_TYPE);

bytes memory newDynamicConfig = abi.encode("newDynamicConfig");

vm.expectEmit();
emit HomeBase.DynamicConfigSet(secondaryConfigDigest, newDynamicConfig);

s_homeBase.setDynamicConfig(DON_ID, PLUGIN_TYPE, newDynamicConfig, secondaryConfigDigest);

(HomeBase.StoredConfig memory storedConfig, bool ok) =
s_homeBase.getStoredConfig(DON_ID, PLUGIN_TYPE, secondaryConfigDigest);
assertTrue(ok);
assertEq(storedConfig.dynamicConfig, newDynamicConfig);

// Asser the digests don't change when updating the dynamic config
(bytes32 primaryDigest, bytes32 secondaryDigest) = s_homeBase.getConfigDigests(DON_ID, PLUGIN_TYPE);
assertEq(primaryDigest, priorPrimaryDigest);
assertEq(secondaryDigest, secondaryConfigDigest);
}

function test_setDynamicConfig_OnlyOwner_reverts() public {
vm.startPrank(address(0));

vm.expectRevert(HomeBase.OnlyOwnerOrSelfCallAllowed.selector);
s_homeBase.setDynamicConfig(DON_ID, PLUGIN_TYPE, _getDynamicConfig(), keccak256("configDigest"));
}
}

contract RMNHome_revokeSecondary is HomeBaseTest {
// Sets two configs
function setUp() public override {
super.setUp();
bytes32 digest = s_homeBase.setSecondary(DON_ID, PLUGIN_TYPE, _getStaticConfig(), _getDynamicConfig(), ZERO_DIGEST);
s_homeBase.promoteSecondaryAndRevokePrimary(DON_ID, PLUGIN_TYPE, digest, ZERO_DIGEST);
s_homeBase.setSecondary(DON_ID, PLUGIN_TYPE, _getStaticConfig(), _getDynamicConfig(), ZERO_DIGEST);
}

function test_revokeSecondary_success() public {
(bytes32 priorPrimaryDigest, bytes32 priorSecondaryDigest) = s_homeBase.getConfigDigests(DON_ID, PLUGIN_TYPE);

vm.expectEmit();
emit HomeBase.ConfigRevoked(priorSecondaryDigest);

s_homeBase.revokeSecondary(DON_ID, PLUGIN_TYPE, priorSecondaryDigest);

(HomeBase.StoredConfig memory storedVersionedConfig, bool ok) =
s_homeBase.getStoredConfig(DON_ID, PLUGIN_TYPE, priorSecondaryDigest);
assertFalse(ok);
// Ensure no old data is returned, even though it's still in storage
assertEq(storedVersionedConfig.version, 0);
assertEq(storedVersionedConfig.staticConfig.length, 0);
assertEq(storedVersionedConfig.dynamicConfig.length, 0);

// Asser the primary digest is unaffected but the secondary digest is set to zero
(bytes32 primaryDigest, bytes32 secondaryDigest) = s_homeBase.getConfigDigests(DON_ID, PLUGIN_TYPE);
assertEq(primaryDigest, priorPrimaryDigest);
assertEq(secondaryDigest, ZERO_DIGEST);
assertTrue(secondaryDigest != priorSecondaryDigest);
}

function test_revokeSecondary_ConfigDigestMismatch_reverts() public {
(, bytes32 priorSecondaryDigest) = s_homeBase.getConfigDigests(DON_ID, PLUGIN_TYPE);

bytes32 wrongDigest = keccak256("wrong_digest");
vm.expectRevert(abi.encodeWithSelector(HomeBase.ConfigDigestMismatch.selector, priorSecondaryDigest, wrongDigest));
s_homeBase.revokeSecondary(DON_ID, PLUGIN_TYPE, wrongDigest);
}

function test_revokeSecondary_OnlyOwner_reverts() public {
vm.startPrank(address(0));

vm.expectRevert(HomeBase.OnlyOwnerOrSelfCallAllowed.selector);
s_homeBase.revokeSecondary(DON_ID, PLUGIN_TYPE, keccak256("configDigest"));
}
}

contract RMNHome_promoteSecondaryAndRevokePrimary is HomeBaseTest {
function test_promoteSecondaryAndRevokePrimary_success() public {}

function test_promoteSecondaryAndRevokePrimary_OnlyOwner_reverts() public {
vm.startPrank(address(0));

vm.expectRevert(HomeBase.OnlyOwnerOrSelfCallAllowed.selector);
s_homeBase.promoteSecondaryAndRevokePrimary(DON_ID, PLUGIN_TYPE, keccak256("toPromote"), keccak256("ToRevoke"));
}
}

contract RMNHome_beforeCapabilityConfigSet is HomeBaseTest {
function test_beforeCapabilityConfigSet_success() public {
vm.startPrank(address(1));

HomeBase.StoredConfig memory encodedConfig = HomeBase.StoredConfig({
configDigest: ZERO_DIGEST,
version: 1,
staticConfig: _getStaticConfig(),
dynamicConfig: _getDynamicConfig()
});
encodedConfig.configDigest = _getConfigDigest(encodedConfig.staticConfig, encodedConfig.version);

bytes memory callPayload = abi.encodeCall(
HomeBase.setSecondary, (DON_ID, PLUGIN_TYPE, encodedConfig.staticConfig, encodedConfig.dynamicConfig, ZERO_DIGEST)
);

vm.expectEmit();
emit HomeBase.ConfigSet(encodedConfig);

s_homeBase.beforeCapabilityConfigSet(new bytes32[](0), callPayload, 0, DON_ID);
}

function test_beforeCapabilityConfigSet_OnlyCapabilitiesRegistryCanCall_reverts() public {
vm.startPrank(address(0));

vm.expectRevert(HomeBase.OnlyCapabilitiesRegistryCanCall.selector);
s_homeBase.beforeCapabilityConfigSet(new bytes32[](0), new bytes(0), 0, DON_ID);
}
}
Loading

0 comments on commit d572c5c

Please sign in to comment.