Skip to content

Commit cecfdac

Browse files
chore(ccip): normalize observer lists on template creation
Reject redundant observer parties when CommitteeVerifier, PerPartyRouter, and RMNRemote instances are created or updated. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 7cc430c commit cecfdac

6 files changed

Lines changed: 120 additions & 3 deletions

File tree

contracts/ccip/committee-verifier/daml/CCIP/CommitteeVerifier.daml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ module CCIP.CommitteeVerifier (
66
import qualified DA.Action
77
import DA.Crypto.Text (BytesHex, PublicKeyHex, byteCount, keccak256, secp256k1WithEcdsaOnly)
88
import DA.Foldable (forA_)
9-
import DA.List (dedup, (\\))
9+
import DA.List (dedup, unique, (\\))
1010
import DA.Map qualified as Map
1111
import DA.Optional (fromOptional, fromSomeNote)
1212

@@ -102,7 +102,9 @@ template CommitteeVerifier
102102
-- proof limit, no duplicate signer keys, and that map keys match the embedded
103103
-- sourceChainSelector in each config.
104104
ensure assertValidInstanceId instanceId
105-
&& versionTag /= "" && all (\(k, config) -> k == config.sourceChainSelector && isValidSignatureConfig config) (Map.toList signerConfigs)
105+
&& versionTag /= ""
106+
&& unique messageSentObservers
107+
&& all (\(k, config) -> k == config.sourceChainSelector && isValidSignatureConfig config) (Map.toList signerConfigs)
106108

107109
interface instance CCIP.Interfaces.CrossChainVerifier.ICrossChainVerifier for CommitteeVerifier where
108110
view = CCIP.Interfaces.CrossChainVerifier.CrossChainVerifierView with
@@ -157,6 +159,8 @@ template CommitteeVerifier
157159
dynamicConfig : DynamicConfig
158160
controller owner
159161
do
162+
assertMsg "SetDynamicConfig: duplicate message sent observers"
163+
(unique dynamicConfig.messageSentObservers)
160164
create this with
161165
allowListAdmin = dynamicConfig.allowListAdmin
162166
messageSentObservers = dynamicConfig.messageSentObservers

contracts/ccip/core/daml/CCIP/RMNRemote.daml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ template RMNRemote
6565
signatory rmnOwner
6666
observer ccipOwner, customObservers
6767
ensure assertValidInstanceId instanceId
68+
&& length customObservers == length (dedup customObservers)
69+
&& rmnOwner `notElem` customObservers
70+
&& ccipOwner `notElem` customObservers
6871

6972
nonconsuming choice Get : RMNRemote
7073
with

contracts/ccip/runtime/daml/CCIP/PerPartyRouter.daml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,10 @@ template PerPartyRouter
292292
where
293293
signatory ccipOwner
294294
observer partyOwner :: customObservers
295-
ensure assertValidInstanceId instanceId && unique customObservers
295+
ensure assertValidInstanceId instanceId
296+
&& unique customObservers
297+
&& ccipOwner `notElem` customObservers
298+
&& partyOwner `notElem` customObservers
296299

297300
nonconsuming choice GetFee : CCIP.OnRamp.GetFeeFromRouterResult
298301
with

contracts/ccip/test/daml/CCIP/RMNTest/RMNRemoteTest.daml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,37 @@ import DA.Assert ((===))
66

77
import CCIP.RMNRemote
88

9+
-- RMN creation rejects redundant custom observers.
10+
testRMNRemote_RejectsRedundantCustomObservers : Script ()
11+
testRMNRemote_RejectsRedundantCustomObservers = script do
12+
rmnOwner <- allocateParty "rmn_owner"
13+
ccipOwner <- allocateParty "ccip_owner"
14+
observer <- allocateParty "observer"
15+
16+
submitMustFail rmnOwner do
17+
createCmd RMNRemote with
18+
instanceId = "rmn-dup-observer-test"
19+
rmnOwner
20+
ccipOwner
21+
customObservers = [observer, observer]
22+
cursedSubjects = []
23+
24+
submitMustFail rmnOwner do
25+
createCmd RMNRemote with
26+
instanceId = "rmn-owner-observer-test"
27+
rmnOwner
28+
ccipOwner
29+
customObservers = [rmnOwner]
30+
cursedSubjects = []
31+
32+
submitMustFail rmnOwner do
33+
createCmd RMNRemote with
34+
instanceId = "rmn-ccip-observer-test"
35+
rmnOwner
36+
ccipOwner
37+
customObservers = [ccipOwner]
38+
cursedSubjects = []
39+
940
-- ===========================================================================
1041
-- UNCURSE MULTIPLE VALIDATION TESTS
1142
-- ===========================================================================

contracts/ccip/test/daml/CommitteeVerifier/Test.daml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,44 @@ finalityConfig n =
2727
then FinalityCodec.WaitForFinality
2828
else FinalityCodec.BlockDepth n
2929

30+
-- Verifier message-sent observer lists stay unique on create/update.
31+
testCommitteeVerifier_RejectsDuplicateMessageSentObservers : Script ()
32+
testCommitteeVerifier_RejectsDuplicateMessageSentObservers = script do
33+
owner <- allocateParty "ccv-observer-owner"
34+
observer <- allocateParty "ccv-message-observer"
35+
36+
let signerConfig = CommitteeVerifier.SignatureConfig with
37+
sourceChainSelector = 123.0
38+
threshold = 1
39+
signerKeys = [head PayloadBuilder.pubkeys]
40+
deps = CommitteeVerifier.CommitteeVerifierDeps with
41+
rmnRemote = RawInstanceAddress.make "ccv-observer-rmn" owner
42+
createVerifier instanceId messageSentObservers = createCmd CommitteeVerifier.CommitteeVerifier with
43+
instanceId
44+
owner
45+
ccipOwner = owner
46+
versionTag = CommitteeVerifier.versionTagV2_0_0
47+
allowListAdmin = None
48+
messageSentObservers
49+
storageLocations = []
50+
storageLocationsAdmin = owner
51+
pendingStorageLocationsAdmin = owner
52+
remoteChainConfigs = Map.empty
53+
signerConfigs = Map.fromList [(123.0, signerConfig)]
54+
deps
55+
56+
submitMustFail owner do
57+
createVerifier "ccv-duplicate-message-observer" [observer, observer]
58+
59+
ccv <- submit owner do
60+
createVerifier "ccv-valid-message-observer" [observer]
61+
62+
submitMustFail owner do
63+
exerciseCmd ccv CommitteeVerifier.SetDynamicConfig with
64+
dynamicConfig = CommitteeVerifier.DynamicConfig with
65+
allowListAdmin = None
66+
messageSentObservers = [observer, observer]
67+
3068
-- | Helper to create a fresh ExecutingMessageV1 for each test.
3169
-- AddCCVVerification is a consuming choice, so each successful verify
3270
-- consumes the contract.

contracts/ccip/test/daml/PerPartyRouter.daml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,44 @@ mult m = intToNumeric (truncate (m * 100000000.0))
9494
cents : Decimal -> Numeric 0
9595
cents c = intToNumeric (truncate c)
9696

97+
-- Direct router creation rejects redundant custom observers.
98+
testPerPartyRouterRejectsRedundantCustomObservers : Script ()
99+
testPerPartyRouterRejectsRedundantCustomObservers = script do
100+
ccipOwner <- allocateParty "router-observer-ccip-owner"
101+
partyOwner <- allocateParty "router-observer-party-owner"
102+
observer <- allocateParty "router-observer-custom"
103+
104+
let deps = CCIP.PerPartyRouter.PerPartyRouterDeps with
105+
onRamp = RawInstanceAddress.make "observer-onramp" ccipOwner
106+
offRamp = RawInstanceAddress.make "observer-offramp" ccipOwner
107+
globalConfig = RawInstanceAddress.make "observer-globalconfig" ccipOwner
108+
tokenAdminRegistry = RawInstanceAddress.make "observer-tar" ccipOwner
109+
feeQuoter = RawInstanceAddress.make "observer-feequoter" ccipOwner
110+
rmnRemote = RawInstanceAddress.make "observer-rmn" ccipOwner
111+
createRouter instanceId customObservers = createCmd CCIP.PerPartyRouter.PerPartyRouter with
112+
instanceId
113+
ccipOwner
114+
partyOwner
115+
deps
116+
outboundSequenceNumbers = Map.empty
117+
executedMessages = Set.empty
118+
archivedExecutionContractIds = Map.empty
119+
customObservers
120+
121+
submitMustFail ccipOwner do
122+
createRouter "router-duplicate-observer" [observer, observer]
123+
124+
submitMustFail ccipOwner do
125+
createRouter "router-ccip-owner-observer" [ccipOwner]
126+
127+
submitMustFail ccipOwner do
128+
createRouter "router-party-owner-observer" [partyOwner]
129+
130+
_ <- submit ccipOwner do
131+
createRouter "router-valid-observer" [observer]
132+
133+
pure ()
134+
97135
basicExtraArgs : Int -> CCIP.Client.ExtraArgs
98136
basicExtraArgs gasLimit = CCIP.Client.V3 CCIP.Client.GenericExtraArgsV3 with
99137
gasLimit = gasLimit

0 commit comments

Comments
 (0)