Skip to content

Commit 0789f6f

Browse files
authored
RMNRemote: curse admin management + legacy rmn removal [CCIP-10871] (#1991)
* Add failing tests * feat: implement curse admin management and access control * refactor: fix formatting of applyCurseAdminUpdates and isCurseAdmin functions * refactor: remove legacy IRMN interface (isBlessed functionality) from RMNRemote contract * fix: regenerating operations * refactor: update comment * refactor: update RMNRemote to use AuthorizedCallers and adjust constructor parameters * refactor: simplify RMNRemote to remove blessing related and OCR related code * refactor: replace IRMNRemote with IRMN in contracts and tests * refactor: remove onlyOwnerOrCurseAdmin modifier and inline validation in curse function * refactor: rename RMNRemote to RMN * fix: go bindings after rename * fix: go bindings after rename - missing generated * fix: make sure only one contract per test. remove unused tests. rename rmnremot to rmn. * refactor: update gas snapshot for RMNRemote tests after renaming and restructuring * refactor: rename RMNRemote test files and update imports to RMNSetup * review comments * gas snapshot * refactor: improve curse function to skip already-cursed subjects and optimize event emission * feat: add RMN deployment changeset (#2008) * feat: add RMN deployment changeset * feat: add test * chore: clean up * feat: create new v2_1_0 directory and move rmn related code under it * fix: old import * feat: add rmn admin management changeset * feat: add cursing/uncursing sequences and changesets * feat: add curse adapter for v2_1_0 * chore: remove unused changeset * feat: add chain-agnostic AuthorizedCallers adapter with EVM implementation (#2038) * feat: add chain-agnostic AuthorizedCallers adapter with EVM implementation Introduce a registry-backed AuthorizedCallers system mirroring the existing fastcurse pattern: - deployment/authorizedcallers: chain-agnostic interface, registry singleton, and ConfigureAuthorizedCallersChangeset - chains/evm/deployment/v2_1_0/adapters: EVMAuthorizedCallersAdapter backed by injected per-contract generated ops so MCMS metadata (ContractType, ABI) is accurate per target contract - init.go registers (EVM, RMN, v2.1.0); additional contracts follow the same NewEVMAuthorizedCallersAdapter pattern * fix: tests * chore: remove non-used changeset in favor of the authorized callers adaptor * fix: bump to latest cl-ccip/deployment * fix: bump to latest cl-ccip/deployment
1 parent f76eec3 commit 0789f6f

48 files changed

Lines changed: 5148 additions & 2091 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

chains/evm/.gas-snapshot

Lines changed: 668 additions & 8 deletions
Large diffs are not rendered by default.

chains/evm/contracts/ccvs/components/BaseVerifier.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
pragma solidity ^0.8.24;
33

44
import {ICrossChainVerifierV1} from "../../interfaces/ICrossChainVerifierV1.sol";
5-
import {IRMNRemote} from "../../interfaces/IRMNRemote.sol";
5+
import {IRMN} from "../../interfaces/IRMN.sol";
66
import {IRouter} from "../../interfaces/IRouter.sol";
77
import {ITypeAndVersion} from "@chainlink/contracts/src/v0.8/shared/interfaces/ITypeAndVersion.sol";
88

@@ -60,7 +60,7 @@ abstract contract BaseVerifier is ICrossChainVerifierV1, ITypeAndVersion {
6060
}
6161

6262
/// @dev The rmn contract.
63-
IRMNRemote internal immutable i_rmn;
63+
IRMN internal immutable i_rmn;
6464
/// @dev The version tag that this instance of the verifier supports. This could be used as a domain separator, but
6565
/// should never be the primary defense against signature replay attacks across different verifiers. The primary
6666
/// defense should be using non-overlapping signer sets for different verifiers, and the version tag should only be
@@ -93,7 +93,7 @@ abstract contract BaseVerifier is ICrossChainVerifierV1, ITypeAndVersion {
9393
if (_versionTag == bytes4(0)) revert VersionTagCannotBeZero();
9494
i_versionTag = _versionTag;
9595

96-
i_rmn = IRMNRemote(rmnAddress);
96+
i_rmn = IRMN(rmnAddress);
9797
}
9898

9999
/// @notice Updates the storage locations.

chains/evm/contracts/interfaces/IRMN.sol

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,17 @@ pragma solidity ^0.8.0;
33

44
/// @notice This interface contains the only RMN-related functions that might be used on-chain by other CCIP contracts.
55
interface IRMN {
6-
/// @notice A Merkle root tagged with the address of the commit store contract it is destined for.
7-
struct TaggedRoot {
8-
address commitStore;
9-
bytes32 root;
10-
}
11-
12-
/// @notice Callers MUST NOT cache the return value as a blessed tagged root could become unblessed.
13-
function isBlessed(
14-
TaggedRoot calldata taggedRoot
15-
) external view returns (bool);
6+
/// @notice gets the current set of cursed subjects.
7+
/// @return subjects the list of cursed subjects.
8+
function getCursedSubjects() external view returns (bytes16[] memory subjects);
169

1710
/// @notice Iff there is an active global or legacy curse, this function returns true.
11+
/// @return bool true if there is an active global curse.
1812
function isCursed() external view returns (bool);
1913

2014
/// @notice Iff there is an active global curse, or an active curse for `subject`, this function returns true.
2115
/// @param subject To check whether a particular chain is cursed, set to bytes16(uint128(chainSelector)).
16+
/// @return bool true if the provided subject is cursed *or* if there is an active global curse.
2217
function isCursed(
2318
bytes16 subject
2419
) external view returns (bool);

chains/evm/contracts/interfaces/IRMNRemote.sol

Lines changed: 0 additions & 39 deletions
This file was deleted.

chains/evm/contracts/offRamp/OffRamp.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {ICrossChainVerifierResolver} from "../interfaces/ICrossChainVerifierReso
77
import {ICrossChainVerifierV1} from "../interfaces/ICrossChainVerifierV1.sol";
88
import {IPoolV1} from "../interfaces/IPool.sol";
99
import {IPoolV2} from "../interfaces/IPoolV2.sol";
10-
import {IRMNRemote} from "../interfaces/IRMNRemote.sol";
10+
import {IRMN} from "../interfaces/IRMN.sol";
1111
import {IRouter} from "../interfaces/IRouter.sol";
1212
import {ITokenAdminRegistry} from "../interfaces/ITokenAdminRegistry.sol";
1313
import {ITypeAndVersion} from "@chainlink/contracts/src/v0.8/shared/interfaces/ITypeAndVersion.sol";
@@ -69,7 +69,7 @@ contract OffRamp is ITypeAndVersion, Ownable2StepMsgSender {
6969
struct StaticConfig {
7070
uint64 localChainSelector; // ──╮ Local chainSelector
7171
uint16 gasForCallExactCheck; // │ Gas for call exact check
72-
IRMNRemote rmnRemote; // ───────╯ RMN Verification Contract
72+
IRMN rmnRemote; // ───────╯ RMN Verification Contract
7373
address tokenAdminRegistry; // ───────╮ Token admin registry address
7474
uint32 maxGasBufferToUpdateState; // ─╯ Max Gas Buffer to Update State
7575
}
@@ -102,7 +102,7 @@ contract OffRamp is ITypeAndVersion, Ownable2StepMsgSender {
102102
/// @dev ChainSelector of this chain.
103103
uint64 internal immutable i_chainSelector;
104104
/// @dev The RMN verification contract.
105-
IRMNRemote internal immutable i_rmnRemote;
105+
IRMN internal immutable i_rmnRemote;
106106
/// @dev The address of the token admin registry.
107107
address internal immutable i_tokenAdminRegistry;
108108
/// @dev The minimum amount of gas to perform the call with exact gas.

chains/evm/contracts/onRamp/OnRamp.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {IExecutor} from "../interfaces/IExecutor.sol";
88
import {IFeeQuoter} from "../interfaces/IFeeQuoter.sol";
99
import {IPoolV1} from "../interfaces/IPool.sol";
1010
import {IPoolV2} from "../interfaces/IPoolV2.sol";
11-
import {IRMNRemote} from "../interfaces/IRMNRemote.sol";
11+
import {IRMN} from "../interfaces/IRMN.sol";
1212
import {IRouter} from "../interfaces/IRouter.sol";
1313
import {ITokenAdminRegistry} from "../interfaces/ITokenAdminRegistry.sol";
1414
import {ITypeAndVersion} from "@chainlink/contracts/src/v0.8/shared/interfaces/ITypeAndVersion.sol";
@@ -78,7 +78,7 @@ contract OnRamp is IEVM2AnyOnRampClient, ITypeAndVersion, Ownable2StepMsgSender
7878
// solhint-disable-next-line gas-struct-packing
7979
struct StaticConfig {
8080
uint64 chainSelector; // ─────────╮ Local chain selector.
81-
IRMNRemote rmnRemote; // │ RMN remote address.
81+
IRMN rmnRemote; // │ RMN remote address.
8282
uint32 maxUSDCentsPerMessage; // ─╯ Maximum USD cent value per message.
8383
address tokenAdminRegistry; // Token admin registry address.
8484
}
@@ -150,7 +150,7 @@ contract OnRamp is IEVM2AnyOnRampClient, ITypeAndVersion, Ownable2StepMsgSender
150150
/// @dev The chain ID of the source chain that this contract is deployed to.
151151
uint64 private immutable i_localChainSelector;
152152
/// @dev The rmn contract.
153-
IRMNRemote private immutable i_rmnRemote;
153+
IRMN private immutable i_rmnRemote;
154154
/// @dev The address of the token admin registry.
155155
address private immutable i_tokenAdminRegistry;
156156
/// @dev The maximum USD cent value per message. Used to reduce impact of potential misconfigurations.

chains/evm/contracts/rmn/RMN.sol

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
pragma solidity ^0.8.24;
3+
4+
import {IRMN} from "../interfaces/IRMN.sol";
5+
import {ITypeAndVersion} from "@chainlink/contracts/src/v0.8/shared/interfaces/ITypeAndVersion.sol";
6+
7+
import {AuthorizedCallers} from "@chainlink/contracts/src/v0.8/shared/access/AuthorizedCallers.sol";
8+
import {EnumerableSet} from "@chainlink/contracts/src/v0.8/shared/enumerable/EnumerableSetWithBytes16.sol";
9+
10+
/// @dev An active curse on this subject will cause isCursed() and isCursed(bytes16) to return true. Use this subject
11+
/// for issues affecting all of CCIP chains, or pertaining to the chain that this contract is deployed on, instead of
12+
/// using the local chain selector as a subject.
13+
bytes16 constant GLOBAL_CURSE_SUBJECT = 0x01000000000000000000000000000001;
14+
15+
/// @notice This contract supports cursing and uncursing of chains.
16+
contract RMN is AuthorizedCallers, ITypeAndVersion, IRMN {
17+
using EnumerableSet for EnumerableSet.Bytes16Set;
18+
19+
error NotCursed(bytes16 subject);
20+
21+
event Cursed(bytes16[] subjects);
22+
event Uncursed(bytes16[] subjects);
23+
24+
string public constant override typeAndVersion = "RMN 2.1.0";
25+
26+
EnumerableSet.Bytes16Set private s_cursedSubjects;
27+
28+
/// @param curseAdmins initial set of addresses authorized to call curse.
29+
constructor(
30+
address[] memory curseAdmins
31+
) AuthorizedCallers(curseAdmins) {}
32+
33+
// ================================================================
34+
// │ Cursing │
35+
// ================================================================
36+
37+
/// @notice Curse a single subject.
38+
/// @param subject The subject to curse.
39+
function curse(
40+
bytes16 subject
41+
) external {
42+
bytes16[] memory subjects = new bytes16[](1);
43+
subjects[0] = subject;
44+
curse(subjects);
45+
}
46+
47+
/// @notice Curse an array of subjects. Already-cursed subjects (including duplicates within the array) are silently
48+
/// skipped so that a single redundant entry does not block the remaining subjects from being cursed.
49+
/// @param subjects the subjects to curse.
50+
function curse(
51+
bytes16[] memory subjects
52+
) public {
53+
// Allow both the owner and authorized callers to curse subjects.
54+
// Skip validation for the owner; validate authorization for others.
55+
if (msg.sender != owner()) {
56+
_validateCaller();
57+
}
58+
// Pre-allocate scratch space equal to the input length; track how many were actually new.
59+
bytes16[] memory newSubjects = new bytes16[](subjects.length);
60+
uint256 count = 0;
61+
for (uint256 i = 0; i < subjects.length; ++i) {
62+
if (s_cursedSubjects.add(subjects[i])) {
63+
newSubjects[count++] = subjects[i];
64+
}
65+
}
66+
if (count == 0) return;
67+
// Truncate the memory array to the number of newly cursed subjects before emitting
68+
assembly {
69+
mstore(newSubjects, count)
70+
}
71+
emit Cursed(newSubjects);
72+
}
73+
74+
/// @notice Uncurse a single subject.
75+
/// @param subject the subject to uncurse.
76+
function uncurse(
77+
bytes16 subject
78+
) external {
79+
bytes16[] memory subjects = new bytes16[](1);
80+
subjects[0] = subject;
81+
uncurse(subjects);
82+
}
83+
84+
/// @notice Uncurse an array of subjects.
85+
/// @param subjects the subjects to uncurse.
86+
/// @dev reverts if any of the subjects are not cursed or if there is a duplicate.
87+
function uncurse(
88+
bytes16[] memory subjects
89+
) public onlyOwner {
90+
for (uint256 i = 0; i < subjects.length; ++i) {
91+
if (!s_cursedSubjects.remove(subjects[i])) {
92+
revert NotCursed(subjects[i]);
93+
}
94+
}
95+
emit Uncursed(subjects);
96+
}
97+
98+
/// @inheritdoc IRMN
99+
function getCursedSubjects() external view returns (bytes16[] memory subjects) {
100+
return s_cursedSubjects.values();
101+
}
102+
103+
/// @inheritdoc IRMN
104+
function isCursed() external view override returns (bool) {
105+
// There are zero curses under normal circumstances, which means it's cheaper to check for the absence of curses.
106+
// than to check the subject list for the global curse subject.
107+
if (s_cursedSubjects.length() == 0) {
108+
return false;
109+
}
110+
return s_cursedSubjects.contains(GLOBAL_CURSE_SUBJECT);
111+
}
112+
113+
/// @inheritdoc IRMN
114+
function isCursed(
115+
bytes16 subject
116+
) external view override returns (bool) {
117+
// There are zero curses under normal circumstances, which means it's cheaper to check for the absence of curses.
118+
// than to check the subject list twice, as we have to check for both the given and global curse subjects.
119+
if (s_cursedSubjects.length() == 0) {
120+
return false;
121+
}
122+
return s_cursedSubjects.contains(subject) || s_cursedSubjects.contains(GLOBAL_CURSE_SUBJECT);
123+
}
124+
}

0 commit comments

Comments
 (0)