Skip to content

Commit

Permalink
MultiOffRamp - size optimizations (#991)
Browse files Browse the repository at this point in the history
## Motivation
Optimizes the contract size for the merged MultiOffRamp + CommitStore to
be able to fit the deployment size limits

## Solution
Currently the margin is at -0.972KB with 3600 optimizer runs. The
contract size fits the space with 1800 optimizer runs.

The contract size can further be reduced, and the optimizer runs
increased after the Nonce Manager is integrated into the MultiOffRamp

Each optimization is split into a separate commit. Complete list:
* Remove in-depth message validations -> these will be moved off-chain
(ticket already created)
* Move `isCursed` to shared internal function
* Move forked chain check, curse check and source config enabled check
to common internal function
* ~~Move source chain config + curse check to single shared function
(used in both exec & commit)~~
* ~~Move zero address check to internal lib~~
* Remove global pausing from the multi-offramp -> there are 4
alternative methods of achieving pausing:
* Per-lane: using `IMessageValidator` (execution-only), disabling the
source chain config, cursing the lane via RMN
  * Globally: implementing the global pause in the RMN
  * The savings are significant, at around ~0.8KB
* Split StaticConfig and DynamicConfig set events
* Get rid of `Any2EVMMessageRoute` and use 2 internal functions to solve
stack depth (same as the OffRamp changes)
* Other contract size golfing: variable caching, inlining
* Compile with lower optimizer runs for the multi-offramp
* **Update**: OCR3 no longer needs a `uniqueReports` distinction
* **Other fix**: OCR3 now uses sequenceNumbers
  • Loading branch information
elatoskinas authored Jun 21, 2024
1 parent f24af78 commit 969d44a
Show file tree
Hide file tree
Showing 13 changed files with 983 additions and 1,293 deletions.
5 changes: 5 additions & 0 deletions contracts/.changeset/gentle-spoons-vanish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@chainlink/contracts-ccip': minor
---

#changed MultiOffRamp contract size optimizations
349 changes: 172 additions & 177 deletions contracts/gas-snapshots/ccip.gas-snapshot

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion contracts/scripts/native_solc_compile_all_ccip
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ SOLC_VERSION="0.8.24"
OPTIMIZE_RUNS=26000
OPTIMIZE_RUNS_OFFRAMP=18000
OPTIMIZE_RUNS_ONRAMP=3600
OPTIMIZE_RUNS_MULTI_OFFRAMP=1800


SCRIPTPATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
Expand All @@ -27,10 +28,14 @@ compileContract () {
local optimize_runs=$OPTIMIZE_RUNS

case $1 in
"ccip/offRamp/EVM2EVMOffRamp.sol" | "ccip/offRamp/EVM2EVMMultiOffRamp.sol")
"ccip/offRamp/EVM2EVMOffRamp.sol")
echo "OffRamp uses $OPTIMIZE_RUNS_OFFRAMP optimizer runs."
optimize_runs=$OPTIMIZE_RUNS_OFFRAMP
;;
"ccip/offRamp/EVM2EVMMultiOffRamp.sol")
echo "MultiOffRamp uses $OPTIMIZE_RUNS_MULTI_OFFRAMP optimizer runs."
optimize_runs=$OPTIMIZE_RUNS_MULTI_OFFRAMP
;;
"ccip/onRamp/EVM2EVMMultiOnRamp.sol" | "ccip/onRamp/EVM2EVMOnRamp.sol")
echo "OnRamp uses $OPTIMIZE_RUNS_ONRAMP optimizer runs."
optimize_runs=$OPTIMIZE_RUNS_ONRAMP
Expand Down
30 changes: 10 additions & 20 deletions contracts/src/v0.8/ccip/ocr/MultiOCR3Base.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ pragma solidity ^0.8.0;
import {OwnerIsCreator} from "../../shared/access/OwnerIsCreator.sol";
import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol";

// TODO: consider splitting configs & verification logic off to auth library (if size is prohibitive)
/// @notice Onchain verification of reports from the offchain reporting protocol
/// with multiple OCR plugin support.
abstract contract MultiOCR3Base is ITypeAndVersion, OwnerIsCreator {
Expand Down Expand Up @@ -50,7 +49,6 @@ abstract contract MultiOCR3Base is ITypeAndVersion, OwnerIsCreator {
bytes32 configDigest;
uint8 F; // ──────────────────────────────╮ maximum number of faulty/dishonest oracles the system can tolerate
uint8 n; // │ number of signers / transmitters
bool uniqueReports; // │ if true, the reports should be unique
bool isSignatureVerificationEnabled; // ──╯ if true, requires signers and verifies signatures on transmission verification
}

Expand Down Expand Up @@ -85,7 +83,6 @@ abstract contract MultiOCR3Base is ITypeAndVersion, OwnerIsCreator {
bytes32 configDigest; // Config digest to update to
uint8 ocrPluginType; // ──────────────────╮ OCR plugin type to update config for
uint8 F; // │ maximum number of faulty/dishonest oracles
bool uniqueReports; // │ if true, the reports should be unique
bool isSignatureVerificationEnabled; // ──╯ if true, requires signers and verifies signatures on transmission verification
address[] signers; // signing address of each oracle
address[] transmitters; // transmission address of each oracle (i.e. the address the oracle actually sends transactions to the contract from)
Expand Down Expand Up @@ -143,12 +140,8 @@ abstract contract MultiOCR3Base is ITypeAndVersion, OwnerIsCreator {

// If F is 0, then the config is not yet set
if (configInfo.F == 0) {
configInfo.uniqueReports = ocrConfigArgs.uniqueReports;
configInfo.isSignatureVerificationEnabled = ocrConfigArgs.isSignatureVerificationEnabled;
} else if (
configInfo.uniqueReports != ocrConfigArgs.uniqueReports
|| configInfo.isSignatureVerificationEnabled != ocrConfigArgs.isSignatureVerificationEnabled
) {
} else if (configInfo.isSignatureVerificationEnabled != ocrConfigArgs.isSignatureVerificationEnabled) {
revert StaticConfigCannotBeChanged(ocrPluginType);
}

Expand Down Expand Up @@ -228,15 +221,13 @@ abstract contract MultiOCR3Base is ITypeAndVersion, OwnerIsCreator {
// TRANSMIT_MSGDATA_CONSTANT_LENGTH_COMPONENT need to be changed accordingly
bytes32[3] calldata reportContext,
bytes calldata report,
// TODO: revisit trade-off - converting this to calldata and using one CONSTANT_LENGTH_COMPONENT
// decreases contract size by ~220B, decreasees commit gas usage by ~400 gas, but increases exec gas usage by ~3600 gas
bytes32[] memory rs,
bytes32[] memory ss,
bytes32 rawVs // signatures
) internal {
// reportContext consists of:
// reportContext[0]: ConfigDigest
// reportContext[1]: 27 byte padding, 4-byte epoch and 1-byte round
// reportContext[1]: 24 byte padding, 8 byte sequence number
// reportContext[2]: ExtraHash
ConfigInfo memory configInfo = s_ocrConfigs[ocrPluginType].configInfo;
bytes32 configDigest = reportContext[0];
Expand All @@ -259,7 +250,7 @@ abstract contract MultiOCR3Base is ITypeAndVersion, OwnerIsCreator {
// If the cached chainID at time of deployment doesn't match the current chainID, we reject all signed reports.
// This avoids a (rare) scenario where chain A forks into chain A and A', A' still has configDigest
// calculated from chain A and so OCR reports will be valid on both forks.
if (i_chainID != block.chainid) revert ForkedChain(i_chainID, block.chainid);
_whenChainNotForked();

// Scoping this reduces stack pressure and gas usage
{
Expand All @@ -278,21 +269,15 @@ abstract contract MultiOCR3Base is ITypeAndVersion, OwnerIsCreator {
if (configInfo.isSignatureVerificationEnabled) {
// Scoping to reduce stack pressure
{
uint256 expectedNumSignatures;
if (configInfo.uniqueReports) {
expectedNumSignatures = (configInfo.n + configInfo.F) / 2 + 1;
} else {
expectedNumSignatures = configInfo.F + 1;
}
if (rs.length != expectedNumSignatures) revert WrongNumberOfSignatures();
if (rs.length != configInfo.F + 1) revert WrongNumberOfSignatures();
if (rs.length != ss.length) revert SignaturesOutOfRegistration();
}

bytes32 h = keccak256(abi.encodePacked(keccak256(report), reportContext));
_verifySignatures(ocrPluginType, h, rs, ss, rawVs);
}

emit Transmitted(ocrPluginType, configDigest, uint32(uint256(reportContext[1]) >> 8));
emit Transmitted(ocrPluginType, configDigest, uint64(uint256(reportContext[1])));
}

/// @notice verifies the signatures of a hashed report value for one OCR plugin type
Expand Down Expand Up @@ -324,6 +309,11 @@ abstract contract MultiOCR3Base is ITypeAndVersion, OwnerIsCreator {
}
}

/// @notice Validates that the chain ID has not diverged after deployment. Reverts if the chain IDs do not match
function _whenChainNotForked() internal view {
if (i_chainID != block.chainid) revert ForkedChain(i_chainID, block.chainid);
}

/// @notice information about current offchain reporting protocol configuration
/// @param ocrPluginType OCR plugin type to return config details for
/// @return ocrConfig OCR config for the plugin type
Expand Down
Loading

0 comments on commit 969d44a

Please sign in to comment.