{"id":19107342,"url":"https://github.com/smartcontractkit/ccip-owner-contracts","last_synced_at":"2025-10-06T12:49:47.466Z","repository":{"id":190238584,"uuid":"681613993","full_name":"smartcontractkit/ccip-owner-contracts","owner":"smartcontractkit","description":"A set of smart contracts used for administering Chainlink contracts, most notably CCIP","archived":false,"fork":false,"pushed_at":"2025-04-03T14:28:29.000Z","size":9472,"stargazers_count":21,"open_issues_count":1,"forks_count":4,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-04-19T08:10:32.165Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Solidity","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/smartcontractkit.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-08-22T11:46:25.000Z","updated_at":"2025-04-03T14:28:33.000Z","dependencies_parsed_at":"2024-01-15T15:34:54.337Z","dependency_job_id":"ad20c5f9-4337-4440-9810-7efec5708b84","html_url":"https://github.com/smartcontractkit/ccip-owner-contracts","commit_stats":null,"previous_names":["smartcontractkit/ccip-owner-contracts"],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smartcontractkit%2Fccip-owner-contracts","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smartcontractkit%2Fccip-owner-contracts/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smartcontractkit%2Fccip-owner-contracts/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smartcontractkit%2Fccip-owner-contracts/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/smartcontractkit","download_url":"https://codeload.github.com/smartcontractkit/ccip-owner-contracts/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251765254,"owners_count":21640160,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-09T04:12:13.889Z","updated_at":"2025-10-06T12:49:42.445Z","avatar_url":"https://github.com/smartcontractkit.png","language":"Solidity","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Chainlink CCIP Owner Contracts\n\nThis repo contains a set of contracts used for administering Chainlink contracts,\nmost notably [CCIP](https://chain.link/cross-chain).\n\nThe contracts in this repo's `main` branch are considered production-grade and\nhave been reviewed as part of a [code4rena contest](https://code4rena.com/contests/2023-07-chainlink-cross-chain-contract-administration-multi-signature-contract-timelock-and-call-proxies#top).\n\n## Development\n\nWe use [foundry](https://book.getfoundry.sh/). The tests rely on some ffi code written in Go 1.18 (see `testCommands/`).\nSee the [official Go docs](https://go.dev/doc/install) for installation instructions.\nOnce you have Go running, `forge test --ffi` should do the trick.\n\nFormat code with `forge fmt`.\n\nGenerate a code coverage report by running `./coverage.sh`.\n\n## Design Considerations\n\nThe `CallProxy`, `ManyChainMultiSig`, `RBACTimelock` contracts are all part of a system of `owner` contracts that is supposed to administer other contracts (henceforth referred to as `OWNED`). `OWNED` contracts represent any system of contracts that (1) have an `owner` or similar role (e.g. using OpenZeppelin's `OwnableInterface`) and that (2) are potentially deployed across multiple chains.\n\n\nHere is a diagram of how we envision these contracts to interact:\n\n```mermaid\ngraph LR;\n    owned[OWNED contracts];\n    prop[ManyChainMultiSig for proposers];\n    cancel[ManyChainMultiSig for cancellers];\n    forwarder[CallProxy];\n    timelock[RBACTimelock];\n    emerg[ManyChainMultiSig for bypassers];\n    prop --\u003e |PROPOSER| timelock;\n    prop --\u003e |CANCELLER| timelock;\n    cancel --\u003e |CANCELLER| timelock;\n    forwarder --\u003e |EXECUTOR| timelock;\n    timelock --\u003e |ADMIN| timelock;\n    timelock --\u003e |OWNER| owned;\n    timelock --\u003e |OWNER| emerg;\n    timelock --\u003e |OWNER| cancel;\n    timelock --\u003e |OWNER| prop;\n    emerg --\u003e |BYPASSER| timelock;\n```\n\nRegular administration of the `OWNED` contracts is expected to happen through\nthe `RBACTimelock`'s Proposer/Executor/Canceller roles. The Bypasser role is\nexpected to only become active in \"break-glass\" type emergency scenarios where\nwaiting for `RBACTimelock.minDelay` would be harmful.\n\nProposers can also cancel so that they may \"undo\" proposals with mistakes in them.\n\nGas cost isn't particularly important for these contracts because they're not expected to\nbe called often. Correctness matters much more.\n\n### `RBACTimelock` Considerations\n\nWe expect to set `RBACTimelock.minDelay` and `delay` to ~ 24 hours, but in general values\nbetween 1 hour and 1 month should be supported.\nThis enables anyone to inspect configuration changes to the `OWNED` contracts before\nthey take effect. For example, a user that disagrees with a configuration change might choose\nto withdraw funds stored in `OWNED` contracts before they can be executed.\n\nWe may use `RBACTimelock.blockFunctionSelector` to prevent specific functions on the\n`OWNED` contracts from being called through the regular propose-execute flow.\n\n`RBACTimelock` is based on an OpenZeppelin contract. We intentionally use the\nold `require` syntax (and some other old techniques) in `RBACTimelock` to keep\nthe diff vs the original OZ contract smaller.\n\n### `CallProxy` Considerations\n\nThe `CallProxy` is intentionally callable by anyone. Offchain tooling used for\ngenerating configuration changes will make appropriate use of the `RBACTimelock`'s\nsupport for `predecessor`s to ensure that configuration changes are sequenced properly\neven if an adversary is executing them. Since the adversary can control the gas amount\nand gas price, callees are expected to not have gas-dependent behavior other than\nreverting if insufficient gas is supplied.\n\nThe `CallProxy` is not expected to be used with contracts that could `SELFDESTRUCT`. It thus has no\n`EXTCODESIZE`-check prior to making a call. We expect it to be configured correctly (i.e. pointing to a real `RBACTimelock`) on deployment.\n\n### `ManyChainMultiSig` Considerations\n\nUnlike standard multi-sig contracts, `ManyChainMultiSig` supports signing many transactions\ntargeting many chains with a single set of signatures. (We currently only target EVM chains\nand all EVM chains support the same ECDSA secp256k1 standard.) This is useful for administering\nsystems of contracts spanning many chains without increasing signing overhead linearly with the\nnumber of supported chains. We expect to use the same set of EOA signers across many chains. Consequently, `ManyChainMultiSig` only supports EOAs as signers, *not* other smart contracts.\nSimilar to the rest of the system, *anyone* who can furnish a correct Merkle proof is allowed to execute authorized calls on the `ManyChainMultiSig`, including a potential adversary. The\nadversary will be able to control the gas price and gas amount for the execution.\n\nThe proposer and canceller `ManyChainMultiSig` contracts are expected to be\nconfigured with a group structure like this, with different sets of signers for each\n(exact k-of-n parameters might differ):\n\n```\n          ┌──────────┐\n          │Root Group│\n      ┌──►│  6-of-8  │◄─────────┐\n      │   └──────────┘          │\n      │         ▲               │\n      │         │               │\n ┌────┴───┐ ┌───┴────┐     ┌────┴───┐\n │signer 1│ │signer 2│ ... │signer 8│\n └────────┘ └────────┘     └────────┘\n```\n\nThe bypasser `ManyChainMultiSig` contract is expected to be configured with a\nmore complex group structure like this (exact structure might differ):\n\n```mermaid\ngraph TD;\n\n    root[root group\u003cbr\u003e2-of-2];\n    sub1[subgroup 1\u003cbr\u003e6-of-8];\n    sub2[subgroup 2\u003cbr\u003e2-of-3];\n    sub21[subgroup 2.1\u003cbr\u003e6-of-8];\n    sub22[subgroup 2.2\u003cbr\u003e1-of-3];\n    sub23[subgroup 2.3\u003cbr\u003e6-of-8];\n    sigs1to8[signers 1 ... 8];\n    sigs9to16[signers 9 ... 16];\n    sigs17to19[signers 17 ... 19];\n    sigs20to27[signers 20 ... 27];\n\n    root --- sub1;\n    root --- sub2;\n    sub2 --- sub21;\n    sub2 --- sub22;\n    sub2 --- sub23;\n    sub1 --- sigs1to8;\n    sub1 --- sigs1to8;\n    sub1 --- sigs1to8;\n    sub1 --- sigs1to8;\n    sub1 --- sigs1to8;\n    sub1 --- sigs1to8;\n    sub1 --- sigs1to8;\n    sub1 --- sigs1to8;\n    sub21 --- sigs9to16;\n    sub21 --- sigs9to16;\n    sub21 --- sigs9to16;\n    sub21 --- sigs9to16;\n    sub21 --- sigs9to16;\n    sub21 --- sigs9to16;\n    sub21 --- sigs9to16;\n    sub21 --- sigs9to16;\n    sub22 --- sigs17to19;\n    sub22 --- sigs17to19;\n    sub22 --- sigs17to19;\n    sub23 --- sigs20to27;\n    sub23 --- sigs20to27;\n    sub23 --- sigs20to27;\n    sub23 --- sigs20to27;\n    sub23 --- sigs20to27;\n    sub23 --- sigs20to27;\n    sub23 --- sigs20to27;\n    sub23 --- sigs20to27;\n```\n\nSubgroup 1 has the same signers as the canceller `ManyChainMultiSig`. No change can ever be enacted\nwithout approval of this group.\n\nIn practice, we expect the k-of-n configurations of groups to typically have `1\u003c=k\u003c=32` and \n`1\u003c=n\u003c=32` (where `k\u003c=n` and we tolerate the overall limits on groups/signers set in \n`ManyChainMultiSig` code).\n\nWe intentionally store chain ids in uint256, foregoing some storage savings. We want to minimize the\nlikelihood of having to change the contract later to support larger chain ids and the cost savings\naren't very significant since we don't envision setting new roots all that frequently.\nWe are aware of a [proposal](https://ethereum-magicians.org/t/eip-2294-explicit-bound-to-chain-id/11090)\nto bound chain ids to 64 bits, but it is still unresolved.\n\nWe choose the tried-and-true OpenZeppelin Merkle tree. We remain conservative and don't make use of\nmultiproofs for the sake of simplicity. The same KISS approach also leads us to not make use of\nMerkle tries with support for storing key-value-pairs (e.g. sparse Merkle trees). Such tries would\nenable us to compute a unique key for each Op, preventing e.g. two Ops with the same nonce and\nmultisig contract from being included in the trie. However, the practical benefit of doing so seems\nlimited: a faulty set of signers can take far more damaging actions that putting two conflicting\ntransactions in the trie.\n\n### Propose-and-Execute Flow\n\nThe following steps need to be performed for a set of onchain maintenance operations on the `OWNED` contracts:\n- [offchain, out of scope] Merkle tree generation \u0026 signing: A Merkle tree containing all the required `ManyChainMultiSig` ops (containing `RBACTimelock.scheduleBatch` calls) for the desired maintenance operations is generated by the proposers. A quorum of signers from the proposer `ManyChainMultiSig` must sign (offchain) the Merkle root.\n- `setRoot` call on all relevant `ManyChainMultiSig` contracts across chains: The signed Merkle root is then sent to `ManyChainMultiSig`s. Anyone who has been given the root and the signatures offchain can send it to `ManyChainMultiSig`s.\n- `execute` on `ManyChainMultiSig`: To propose an action to the `RBACTimelock`, a multi-sig op is executed by providing a Merkle proof for that specific op. Anyone who has been given the full Merkle tree offchain can propose the action.\n- `executeBatch` on `RBACTimelock`: After the timelock wait period expires, the proposed actions in TimeLock can be executed. This assumes that the cancellers have not cancelled them in the meantime. Anyone can execute the actions because all the required information is available on the blockchain through event logs.\n\n### Canceller Flow\n\nThis can be thought of as an optional step of the propose-and-execute flow. If a quorum of cancellers disapproves of an action pending on the\n`RBACTimelock`, they can create a set of `ManyChainMultiSig.Op`s that calls `RBACTimelock.cancel` on\nall relevant `RBACTimelock`s.\n\n### Bypasser Flow\n\nThis is completely independent of the propose-and-execute flow.\nBypassers create a set of `ManyChainMultiSig.Op`s that calls `RBACTimelock.bypasserExecuteBatch` on\nall relevant `RBACTimelock`s.\n\n## Porting\n\nDevelopers porting ManyChainMultiSig (MCMS) to new target chains should keep the following considerations in mind.\n\n- If the target chain supports keccak256 and secp256k1, as well as sufficient programmability to mirror the Solidity contract's Merkle tree computations, then the same set of signers as on the Solidity contract can be used. We expect that most target chains will fall in this category as they aim to be compatible with existing Ethereum signing infrastructure.\nThis option is recommended, so that signers only have to sign once, and due to the prevalence of `eth_sign` support in hardware wallets, which may be used by the multisig signers.\n- Use distinct domain separators for the Merkle tree leaves for each target chain family to avoid ambiguity.\n  - Use distinct domain separators for metadata, for each target chain family (e.g., `keccak256(\"MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_METADATA_SOLANA\")`).\n  - Use distinct domain separators for ops, for each target chain family (e.g., `keccak256(\"MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_OP_SOLANA\")`).\n  - This prevents ops and metadata for a chain family from being replayable on another.\n- Preimages of Merkle tree leaves must conform to the following rules:\n  - The domain separator must always be the first word (32 bytes) of any leaf preimage.\n  - The rest of the leaf preimage can be encoded in any way that makes sense for the target chain and language, i.e., does not need to follow the same abi encoding of the Solidity contract.\n  - The encoding must be canonical:\n    - It must not be possible for two distinct metadata to encode to the same value.\n    - It must not be possible for two distinct ops to encode to the same value.\n  - Any leaf preimage must always be of length greater than 64 bytes, to avoid ambiguity with internal nodes.\n- Computation of internal nodes of the Merkle tree must happen identically to the Solidity contract, in order to reuse the Solidity Merkle tree. In particular, the internal node hash should be computed as the commutative keccak256 hash of its two subtree root hashes. See [this snippet](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/e50c24f5839db17f46991478384bfda14acfb830/contracts/utils/cryptography/MerkleProof.sol#L215-L217) from OpenZeppelin for the exact implementation depended upon by the Solidity contract.\n- For chains that have some notion similar to the [EVM chain id](https://eips.ethereum.org/EIPS/eip-155), and where the chain id is available in the target language environment (similar to `block.chainid`), the chain id must be used for both metadata and ops. If such a chain id does not exist, please contact the authors for guidance. We prefer use of chain ids to chain selectors for MCMS because:\n  - Chain ids gracefully handle permanent forks. If an entity decides to fork a chain (e.g., as in the case of Ethereum \u0026 Ethereum Classic), it is considered good practice for them to assign a different chain id to their fork to avoid replayability of transactions across forks. In such a scenario, use of chain ids in MCMS avoids replayability of MCMS metadata and ops across forks.\n  - Chain selectors must be configured during deployment, and if misconfigured can cause MCMS metadata and ops to be executable on an unintended chain.\n- If unsure whether a target chain warrants a new chain family, reach out to the authors. Even if two target chains use the same language, they might not belong to the same family (e.g., Aptos \u0026 Sui both support Move as a language, but they belong to different chain families). Always ensure that there can never be two chains with the same chain id in the same chain family.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsmartcontractkit%2Fccip-owner-contracts","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsmartcontractkit%2Fccip-owner-contracts","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsmartcontractkit%2Fccip-owner-contracts/lists"}