Skip to content

Commit

Permalink
Create multi chain copies for on/off ramps, ARL & commit store (#772)
Browse files Browse the repository at this point in the history
## Motivation

Create multi chain copies for the below contracts:
- `EVM2EVMOffRamp` -> `EVM2EVMMultiOffRamp`
- `EVM2EVMOnRamp` -> `EVM2EVMMultiOnRamp`
- `AggregateRateLimiter` -> `MultiAggregateRateLimiter`
- `CommitStore` -> `MultiCommitStore`

## Solution

New contracts are exact copies of the single chain ones.

Some minor changes for CI purposes:
- Removed some unused imports
- Increased solhint max warnings

---------

Co-authored-by: Evaldas Latoskinas <evaldas.latoskinas@smartcontract.com>
  • Loading branch information
RayXpub and elatoskinas committed May 3, 2024
1 parent 07337eb commit 71ae754
Show file tree
Hide file tree
Showing 10 changed files with 2,681 additions and 29 deletions.
5 changes: 5 additions & 0 deletions contracts/.changeset/funny-monkeys-crash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@chainlink/contracts-ccip": minor
---

create multi chain copies for on/off ramps, ARL & commit store
532 changes: 504 additions & 28 deletions contracts/.gas-snapshot

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions contracts/gas-snapshots/ccip.gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,20 @@ MerkleMultiProofTest:test_EmptyLeaf_Revert() (gas: 3574)
MerkleMultiProofTest:test_MerkleRoot256() (gas: 411772)
MerkleMultiProofTest:test_MerkleRootSingleLeaf_Success() (gas: 3649)
MerkleMultiProofTest:test_SpecSync_gas() (gas: 34123)
MultiAggregateRateLimiter__getTokenValue:test_GetTokenValue_Success() (gas: 17308)
MultiAggregateRateLimiter__getTokenValue:test_NoTokenPrice_Reverts() (gas: 18887)
MultiAggregateRateLimiter__rateLimitValue:test_AggregateValueMaxCapacityExceeded_Revert() (gas: 16391)
MultiAggregateRateLimiter__rateLimitValue:test_RateLimitValueSuccess_gas() (gas: 18288)
MultiAggregateRateLimiter_constructor:test_Constructor_Success() (gas: 26914)
MultiAggregateRateLimiter_getTokenBucket:test_GetTokenBucket_Success() (gas: 19685)
MultiAggregateRateLimiter_getTokenBucket:test_Refill_Success() (gas: 40881)
MultiAggregateRateLimiter_getTokenBucket:test_TimeUnderflow_Revert() (gas: 15347)
MultiAggregateRateLimiter_getTokenLimitAdmin:test_GetTokenLimitAdmin_Success() (gas: 10531)
MultiAggregateRateLimiter_setAdmin:test_OnlyOwnerOrAdmin_Revert() (gas: 13047)
MultiAggregateRateLimiter_setAdmin:test_Owner_Success() (gas: 18989)
MultiAggregateRateLimiter_setRateLimiterConfig:test_OnlyOnlyCallableByAdminOrOwner_Revert() (gas: 17479)
MultiAggregateRateLimiter_setRateLimiterConfig:test_Owner_Success() (gas: 30043)
MultiAggregateRateLimiter_setRateLimiterConfig:test_TokenLimitAdmin_Success() (gas: 32052)
OCR2BaseNoChecks_setOCR2Config:test_FMustBePositive_Revert() (gas: 12278)
OCR2BaseNoChecks_setOCR2Config:test_RepeatAddress_Revert() (gas: 42395)
OCR2BaseNoChecks_setOCR2Config:test_SetConfigSuccess_gas() (gas: 84878)
Expand Down
2 changes: 1 addition & 1 deletion contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"publish-beta": "pnpm publish --tag beta",
"publish-prod": "npm dist-tag add @chainlink/contracts-ccip@1.2.1 latest",
"lint:ccip": "solhint --config ./src/v0.8/ccip/.solhint.json --ignore-path .ccip-solhint-ignore --max-warnings 0 \"./src/v0.8/ccip/**/*.sol\"",
"solhint": "solhint --max-warnings 92 \"./src/v0.8/**/*.sol\""
"solhint": "solhint --max-warnings 95 \"./src/v0.8/**/*.sol\""
},
"files": [
"src/v0.8/ccip/**/*.sol",
Expand Down
93 changes: 93 additions & 0 deletions contracts/src/v0.8/ccip/MultiAggregateRateLimiter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.19;

import {IPriceRegistry} from "./interfaces/IPriceRegistry.sol";

import {OwnerIsCreator} from "./../shared/access/OwnerIsCreator.sol";
import {Client} from "./libraries/Client.sol";
import {RateLimiter} from "./libraries/RateLimiter.sol";
import {USDPriceWith18Decimals} from "./libraries/USDPriceWith18Decimals.sol";

/// @notice The aggregate rate limiter is a wrapper of the token bucket rate limiter
/// which permits rate limiting based on the aggregate value of a group of
/// token transfers, using a price registry to convert to a numeraire asset (e.g. USD).
contract MultiAggregateRateLimiter is OwnerIsCreator {
using RateLimiter for RateLimiter.TokenBucket;
using USDPriceWith18Decimals for uint224;

error PriceNotFoundForToken(address token);

event AdminSet(address newAdmin);

// The address of the token limit admin that has the same permissions as the owner.
address internal s_admin;

// The token bucket object that contains the bucket state.
RateLimiter.TokenBucket private s_rateLimiter;

/// @param config The RateLimiter.Config containing the capacity and refill rate
/// of the bucket, plus the admin address.
constructor(RateLimiter.Config memory config) {
s_rateLimiter = RateLimiter.TokenBucket({
rate: config.rate,
capacity: config.capacity,
tokens: config.capacity,
lastUpdated: uint32(block.timestamp),
isEnabled: config.isEnabled
});
}

/// @notice Consumes value from the rate limiter bucket based on the token value given.
function _rateLimitValue(uint256 value) internal {
s_rateLimiter._consume(value, address(0));
}

function _getTokenValue(
Client.EVMTokenAmount memory tokenAmount,
IPriceRegistry priceRegistry
) internal view returns (uint256) {
// not fetching validated price, as price staleness is not important for value-based rate limiting
// we only need to verify the price is not 0
uint224 pricePerToken = priceRegistry.getTokenPrice(tokenAmount.token).value;
if (pricePerToken == 0) revert PriceNotFoundForToken(tokenAmount.token);
return pricePerToken._calcUSDValueFromTokenAmount(tokenAmount.amount);
}

/// @notice Gets the token bucket with its values for the block it was requested at.
/// @return The token bucket.
function currentRateLimiterState() external view returns (RateLimiter.TokenBucket memory) {
return s_rateLimiter._currentTokenBucketState();
}

/// @notice Sets the rate limited config.
/// @param config The new rate limiter config.
/// @dev should only be callable by the owner or token limit admin.
function setRateLimiterConfig(RateLimiter.Config memory config) external onlyAdminOrOwner {
s_rateLimiter._setTokenBucketConfig(config);
}

// ================================================================
// │ Access │
// ================================================================

/// @notice Gets the token limit admin address.
/// @return the token limit admin address.
function getTokenLimitAdmin() external view returns (address) {
return s_admin;
}

/// @notice Sets the token limit admin address.
/// @param newAdmin the address of the new admin.
/// @dev setting this to address(0) indicates there is no active admin.
function setAdmin(address newAdmin) external onlyAdminOrOwner {
s_admin = newAdmin;
emit AdminSet(newAdmin);
}

/// @notice a modifier that allows the owner or the s_tokenLimitAdmin call the functions
/// it is applied to.
modifier onlyAdminOrOwner() {
if (msg.sender != owner() && msg.sender != s_admin) revert RateLimiter.OnlyCallableByAdminOrOwner();
_;
}
}

0 comments on commit 71ae754

Please sign in to comment.