Skip to content

Commit

Permalink
improve test
Browse files Browse the repository at this point in the history
  • Loading branch information
RensR committed Sep 26, 2024
1 parent ef30652 commit 98b61fa
Show file tree
Hide file tree
Showing 2 changed files with 358 additions and 336 deletions.
364 changes: 358 additions & 6 deletions contracts/src/v0.8/ccip/test/rmn/RMNHome.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,365 @@ pragma solidity 0.8.24;

import {Internal} from "../../libraries/Internal.sol";
import {RMNHome} from "../../rmn/RMNHome.sol";
import {BaseTest} from "../BaseTest.t.sol";
import {Test} from "forge-std/Test.sol";
import {Vm} from "forge-std/Vm.sol";

contract RMNHomeTest is BaseTest {
RMNHome public s_rmnHome;
contract RMNHomeTest is Test {
struct Config {
RMNHome.StaticConfig staticConfig;
RMNHome.DynamicConfig dynamicConfig;
}

bytes32 internal constant ZERO_DIGEST = bytes32(uint256(0));
RMNHome public s_rmnHome = new RMNHome();

function _getBaseConfig() internal pure returns (Config memory) {
RMNHome.Node[] memory nodes = new RMNHome.Node[](3);
nodes[0] = RMNHome.Node({peerId: keccak256("peerId_0"), offchainPublicKey: keccak256("offchainPublicKey_0")});
nodes[1] = RMNHome.Node({peerId: keccak256("peerId_1"), offchainPublicKey: keccak256("offchainPublicKey_1")});
nodes[2] = RMNHome.Node({peerId: keccak256("peerId_2"), offchainPublicKey: keccak256("offchainPublicKey_2")});

RMNHome.SourceChain[] memory sourceChains = new RMNHome.SourceChain[](2);
// Observer 0 for source chain 9000
sourceChains[0] = RMNHome.SourceChain({chainSelector: 9000, minObservers: 1, observerNodesBitmap: 1 << 0});
// Observers 1 and 2 for source chain 9001
sourceChains[1] = RMNHome.SourceChain({chainSelector: 9001, minObservers: 2, observerNodesBitmap: 1 << 1 | 1 << 2});

return Config({
staticConfig: RMNHome.StaticConfig({nodes: nodes, offchainConfig: abi.encode("static_config")}),
dynamicConfig: RMNHome.DynamicConfig({sourceChains: sourceChains, offchainConfig: abi.encode("dynamic_config")})
});
}

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

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_rmnHome), version), staticConfig))
) & ~PREFIX_MASK
)
);
}
}

contract RMNHome_getConfigDigests is RMNHomeTest {
function test_getConfigDigests_success() public {
(bytes32 activeDigest, bytes32 candidateDigest) = s_rmnHome.getConfigDigests();
assertEq(activeDigest, ZERO_DIGEST);
assertEq(candidateDigest, ZERO_DIGEST);

Config memory config = _getBaseConfig();
bytes32 firstDigest = s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST);

(activeDigest, candidateDigest) = s_rmnHome.getConfigDigests();
assertEq(activeDigest, ZERO_DIGEST);
assertEq(candidateDigest, firstDigest);

s_rmnHome.promoteCandidateAndRevokeActive(firstDigest, ZERO_DIGEST);

(activeDigest, candidateDigest) = s_rmnHome.getConfigDigests();
assertEq(activeDigest, firstDigest);
assertEq(candidateDigest, ZERO_DIGEST);

bytes32 secondDigest = s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST);

(activeDigest, candidateDigest) = s_rmnHome.getConfigDigests();
assertEq(activeDigest, firstDigest);
assertEq(candidateDigest, secondDigest);

assertEq(activeDigest, s_rmnHome.getActiveDigest());
assertEq(candidateDigest, s_rmnHome.getCandidateDigest());
}
}

contract RMNHome_setCandidate is RMNHomeTest {
function test_setCandidate_success() public {
Config memory config = _getBaseConfig();
RMNHome.VersionedConfig memory versionedConfig = RMNHome.VersionedConfig({
version: 1,
staticConfig: config.staticConfig,
dynamicConfig: config.dynamicConfig,
configDigest: ZERO_DIGEST
});

versionedConfig.configDigest = _getConfigDigest(abi.encode(versionedConfig.staticConfig), versionedConfig.version);

vm.expectEmit();
emit RMNHome.ConfigSet(
versionedConfig.configDigest, versionedConfig.version, versionedConfig.staticConfig, versionedConfig.dynamicConfig
);

s_rmnHome.setCandidate(versionedConfig.staticConfig, versionedConfig.dynamicConfig, ZERO_DIGEST);

(RMNHome.VersionedConfig memory storedVersionedConfig, bool ok) = s_rmnHome.getConfig(versionedConfig.configDigest);
assertTrue(ok);
assertEq(storedVersionedConfig.version, versionedConfig.version);
RMNHome.StaticConfig memory storedStaticConfig = storedVersionedConfig.staticConfig;
RMNHome.DynamicConfig memory storedDynamicConfig = storedVersionedConfig.dynamicConfig;

assertEq(storedStaticConfig.nodes.length, versionedConfig.staticConfig.nodes.length);
for (uint256 i = 0; i < storedStaticConfig.nodes.length; i++) {
RMNHome.Node memory storedNode = storedStaticConfig.nodes[i];
assertEq(storedNode.peerId, versionedConfig.staticConfig.nodes[i].peerId);
assertEq(storedNode.offchainPublicKey, versionedConfig.staticConfig.nodes[i].offchainPublicKey);
}

assertEq(storedDynamicConfig.sourceChains.length, versionedConfig.dynamicConfig.sourceChains.length);
for (uint256 i = 0; i < storedDynamicConfig.sourceChains.length; i++) {
RMNHome.SourceChain memory storedSourceChain = storedDynamicConfig.sourceChains[i];
assertEq(storedSourceChain.chainSelector, versionedConfig.dynamicConfig.sourceChains[i].chainSelector);
assertEq(storedSourceChain.minObservers, versionedConfig.dynamicConfig.sourceChains[i].minObservers);
assertEq(storedSourceChain.observerNodesBitmap, versionedConfig.dynamicConfig.sourceChains[i].observerNodesBitmap);
}
assertEq(storedDynamicConfig.offchainConfig, versionedConfig.dynamicConfig.offchainConfig);
assertEq(storedStaticConfig.offchainConfig, versionedConfig.staticConfig.offchainConfig);
}

function test_setCandidate_ConfigDigestMismatch_reverts() public {
Config memory config = _getBaseConfig();

bytes32 digest = s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST);

vm.expectRevert(abi.encodeWithSelector(RMNHome.ConfigDigestMismatch.selector, digest, ZERO_DIGEST));
s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST);

vm.expectEmit();
emit RMNHome.ConfigRevoked(digest);

s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, digest);
}

function test_setCandidate_OnlyOwner_reverts() public {
Config memory config = _getBaseConfig();

vm.startPrank(address(0));

vm.expectRevert("Only callable by owner");
s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST);
}
}

contract RMNHome_revokeCandidate is RMNHomeTest {
// Sets two configs
function setUp() public {
Config memory config = _getBaseConfig();
bytes32 digest = s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST);
s_rmnHome.promoteCandidateAndRevokeActive(digest, ZERO_DIGEST);

config.dynamicConfig.sourceChains[0].minObservers--;
s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST);
}

function test_revokeCandidate_success() public {
(bytes32 priorActiveDigest, bytes32 priorCandidateDigest) = s_rmnHome.getConfigDigests();

vm.expectEmit();
emit RMNHome.ConfigRevoked(priorCandidateDigest);

s_rmnHome.revokeCandidate(priorCandidateDigest);

(RMNHome.VersionedConfig memory storedVersionedConfig, bool ok) = s_rmnHome.getConfig(priorCandidateDigest);
assertFalse(ok);
// Ensure no old data is returned, even though it's still in storage
assertEq(storedVersionedConfig.version, 0);
assertEq(storedVersionedConfig.staticConfig.nodes.length, 0);
assertEq(storedVersionedConfig.dynamicConfig.sourceChains.length, 0);

// Asser the active digest is unaffected but the candidate digest is set to zero
(bytes32 activeDigest, bytes32 candidateDigest) = s_rmnHome.getConfigDigests();
assertEq(activeDigest, priorActiveDigest);
assertEq(candidateDigest, ZERO_DIGEST);
assertTrue(candidateDigest != priorCandidateDigest);
}

function test_revokeCandidate_ConfigDigestMismatch_reverts() public {
(, bytes32 priorCandidateDigest) = s_rmnHome.getConfigDigests();

bytes32 wrongDigest = keccak256("wrong_digest");
vm.expectRevert(abi.encodeWithSelector(RMNHome.ConfigDigestMismatch.selector, priorCandidateDigest, wrongDigest));
s_rmnHome.revokeCandidate(wrongDigest);
}

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

vm.expectRevert("Only callable by owner");
s_rmnHome.revokeCandidate(keccak256("configDigest"));
}
}

contract RMNHome_promoteCandidateAndRevokeActive is RMNHomeTest {
function test_promoteCandidateAndRevokeActive_success() public {
Config memory config = _getBaseConfig();
bytes32 firstConfigToPromote = s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST);

vm.expectEmit();
emit RMNHome.ConfigPromoted(firstConfigToPromote);

s_rmnHome.promoteCandidateAndRevokeActive(firstConfigToPromote, ZERO_DIGEST);

// Assert the active digest is updated and the candidate digest is set to zero
(bytes32 activeDigest, bytes32 candidateDigest) = s_rmnHome.getConfigDigests();
assertEq(activeDigest, firstConfigToPromote);
assertEq(candidateDigest, ZERO_DIGEST);

// Set a new candidate to promote over a non-zero active config.
config.staticConfig.offchainConfig = abi.encode("new_static_config");
config.dynamicConfig.offchainConfig = abi.encode("new_dynamic_config");
bytes32 secondConfigToPromote = s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST);

vm.expectEmit();
emit RMNHome.ConfigRevoked(firstConfigToPromote);

vm.expectEmit();
emit RMNHome.ConfigPromoted(secondConfigToPromote);

s_rmnHome.promoteCandidateAndRevokeActive(secondConfigToPromote, firstConfigToPromote);

(RMNHome.VersionedConfig memory activeConfig, RMNHome.VersionedConfig memory candidateConfig) =
s_rmnHome.getAllConfigs();
assertEq(activeConfig.configDigest, secondConfigToPromote);
assertEq(activeConfig.staticConfig.offchainConfig, config.staticConfig.offchainConfig);
assertEq(activeConfig.dynamicConfig.offchainConfig, config.dynamicConfig.offchainConfig);

assertEq(candidateConfig.configDigest, ZERO_DIGEST);
}

function test_promoteCandidateAndRevokeActive_ConfigDigestMismatch_reverts() public {
(bytes32 priorActiveDigest, bytes32 priorCandidateDigest) = s_rmnHome.getConfigDigests();
bytes32 wrongActiveDigest = keccak256("wrongActiveDigest");
bytes32 wrongCandidateDigest = keccak256("wrongCandidateDigest");

vm.expectRevert(
abi.encodeWithSelector(RMNHome.ConfigDigestMismatch.selector, priorActiveDigest, wrongCandidateDigest)
);
s_rmnHome.promoteCandidateAndRevokeActive(wrongCandidateDigest, wrongActiveDigest);

vm.expectRevert(abi.encodeWithSelector(RMNHome.ConfigDigestMismatch.selector, priorActiveDigest, wrongActiveDigest));

s_rmnHome.promoteCandidateAndRevokeActive(priorCandidateDigest, wrongActiveDigest);
}

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

vm.expectRevert("Only callable by owner");
s_rmnHome.promoteCandidateAndRevokeActive(keccak256("toPromote"), keccak256("ToRevoke"));
}
}

contract RMNHome__validateStaticAndDynamicConfig is RMNHomeTest {
function test_validateStaticAndDynamicConfig_OutOfBoundsNodesLength_reverts() public {
Config memory config = _getBaseConfig();
config.staticConfig.nodes = new RMNHome.Node[](257);

vm.expectRevert(RMNHome.OutOfBoundsNodesLength.selector);
s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST);
}

function test_validateStaticAndDynamicConfig_DuplicatePeerId_reverts() public {
Config memory config = _getBaseConfig();
config.staticConfig.nodes[1].peerId = config.staticConfig.nodes[0].peerId;

vm.expectRevert(RMNHome.DuplicatePeerId.selector);
s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST);
}

function test_validateStaticAndDynamicConfig_DuplicateOffchainPublicKey_reverts() public {
Config memory config = _getBaseConfig();
config.staticConfig.nodes[1].offchainPublicKey = config.staticConfig.nodes[0].offchainPublicKey;

vm.expectRevert(RMNHome.DuplicateOffchainPublicKey.selector);
s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST);
}

function test_validateStaticAndDynamicConfig_DuplicateSourceChain_reverts() public {
Config memory config = _getBaseConfig();
config.dynamicConfig.sourceChains[1].chainSelector = config.dynamicConfig.sourceChains[0].chainSelector;

vm.expectRevert(RMNHome.DuplicateSourceChain.selector);
s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST);
}

function test_validateStaticAndDynamicConfig_OutOfBoundsObserverNodeIndex_reverts() public {
Config memory config = _getBaseConfig();
config.dynamicConfig.sourceChains[0].observerNodesBitmap = 1 << config.staticConfig.nodes.length;

vm.expectRevert(RMNHome.OutOfBoundsObserverNodeIndex.selector);
s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST);
}

function test_validateStaticAndDynamicConfig_MinObserversTooHigh_reverts() public {
Config memory config = _getBaseConfig();
config.dynamicConfig.sourceChains[0].minObservers++;

vm.expectRevert(RMNHome.MinObserversTooHigh.selector);
s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST);
}
}

contract RMNHome_setDynamicConfig is RMNHomeTest {
function setUp() public {
Config memory config = _getBaseConfig();
s_rmnHome.setCandidate(config.staticConfig, config.dynamicConfig, ZERO_DIGEST);
}

function test_setDynamicConfig_success() public {
(bytes32 priorActiveDigest,) = s_rmnHome.getConfigDigests();

Config memory config = _getBaseConfig();
config.dynamicConfig.sourceChains[0].minObservers--;

(, bytes32 candidateConfigDigest) = s_rmnHome.getConfigDigests();

vm.expectEmit();
emit RMNHome.DynamicConfigSet(candidateConfigDigest, config.dynamicConfig);

s_rmnHome.setDynamicConfig(config.dynamicConfig, candidateConfigDigest);

(RMNHome.VersionedConfig memory storedVersionedConfig, bool ok) = s_rmnHome.getConfig(candidateConfigDigest);
assertTrue(ok);
assertEq(
storedVersionedConfig.dynamicConfig.sourceChains[0].minObservers,
config.dynamicConfig.sourceChains[0].minObservers
);

// Asser the digests don't change when updating the dynamic config
(bytes32 activeDigest, bytes32 candidateDigest) = s_rmnHome.getConfigDigests();
assertEq(activeDigest, priorActiveDigest);
assertEq(candidateDigest, candidateConfigDigest);
}

// Asserts the validation function is being called
function test_setDynamicConfig_MinObserversTooHigh_reverts() public {
Config memory config = _getBaseConfig();
config.dynamicConfig.sourceChains[0].minObservers++;

vm.expectRevert(abi.encodeWithSelector(RMNHome.DigestNotFound.selector, ZERO_DIGEST));
s_rmnHome.setDynamicConfig(config.dynamicConfig, ZERO_DIGEST);
}

function test_setDynamicConfig_DigestNotFound_reverts() public {
// Zero always reverts
vm.expectRevert(abi.encodeWithSelector(RMNHome.DigestNotFound.selector, ZERO_DIGEST));
s_rmnHome.setDynamicConfig(_getBaseConfig().dynamicConfig, ZERO_DIGEST);

// Non-existent digest reverts
bytes32 nonExistentDigest = keccak256("nonExistentDigest");
vm.expectRevert(abi.encodeWithSelector(RMNHome.DigestNotFound.selector, nonExistentDigest));
s_rmnHome.setDynamicConfig(_getBaseConfig().dynamicConfig, nonExistentDigest);
}

function test_setDynamicConfig_OnlyOwner_reverts() public {
Config memory config = _getBaseConfig();

vm.startPrank(address(0));

function setUp() public virtual override {
super.setUp();
s_rmnHome = new RMNHome();
vm.expectRevert("Only callable by owner");
s_rmnHome.setDynamicConfig(config.dynamicConfig, keccak256("configDigest"));
}
}
Loading

0 comments on commit 98b61fa

Please sign in to comment.