Skip to content

Commit

Permalink
add gho
Browse files Browse the repository at this point in the history
  • Loading branch information
RensR committed Oct 25, 2024
1 parent 8c38a18 commit 0bd1198
Show file tree
Hide file tree
Showing 2 changed files with 641 additions and 0 deletions.
364 changes: 364 additions & 0 deletions contracts/src/v0.8/ccip/pools/GHO/UpgradeableTokenPool.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,364 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

import {IPoolV1} from "../../interfaces/IPool.sol";
import {IRMN} from "../../interfaces/IRMN.sol";
import {IRouter} from "../../interfaces/IRouter.sol";

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

import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol";
import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/introspection/IERC165.sol";
import {EnumerableSet} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/structs/EnumerableSet.sol";

/// @title UpgradeableTokenPool
/// @author Aave Labs
/// @notice Upgradeable version of Chainlink's CCIP TokenPool
/// @dev Contract adaptations:
/// - Setters & Getters for new ProxyPool (to support 1.5 CCIP migration on the existing 1.4 Pool)
/// - Modify `onlyOnRamp` modifier to accept transactions from ProxyPool
abstract contract UpgradeableTokenPool is IPoolV1, OwnerIsCreator {
using EnumerableSet for EnumerableSet.AddressSet;
using EnumerableSet for EnumerableSet.UintSet;
using RateLimiter for RateLimiter.TokenBucket;

error CallerIsNotARampOnRouter(address caller);
error ZeroAddressNotAllowed();
error SenderNotAllowed(address sender);
error AllowListNotEnabled();
error NonExistentChain(uint64 remoteChainSelector);
error ChainNotAllowed(uint64 remoteChainSelector);
error BadARMSignal();
error ChainAlreadyExists(uint64 chainSelector);

event Locked(address indexed sender, uint256 amount);
event Burned(address indexed sender, uint256 amount);
event Released(address indexed sender, address indexed recipient, uint256 amount);
event Minted(address indexed sender, address indexed recipient, uint256 amount);
event ChainAdded(
uint64 remoteChainSelector,
RateLimiter.Config outboundRateLimiterConfig,
RateLimiter.Config inboundRateLimiterConfig
);
event ChainConfigured(
uint64 remoteChainSelector,
RateLimiter.Config outboundRateLimiterConfig,
RateLimiter.Config inboundRateLimiterConfig
);
event ChainRemoved(uint64 remoteChainSelector);
event AllowListAdd(address sender);
event AllowListRemove(address sender);
event RouterUpdated(address oldRouter, address newRouter);

struct ChainUpdate {
uint64 remoteChainSelector; // ──╮ Remote chain selector
bool allowed; // ────────────────╯ Whether the chain is allowed
RateLimiter.Config outboundRateLimiterConfig; // Outbound rate limited config, meaning the rate limits for all of the onRamps for the given chain
RateLimiter.Config inboundRateLimiterConfig; // Inbound rate limited config, meaning the rate limits for all of the offRamps for the given chain
}

/// @dev The storage slot for Proxy Pool address, act as an on ramp "wrapper" post ccip 1.5 migration.
/// @dev This was added to continue support for 1.2 onRamp during 1.5 migration, and is stored
/// this way to avoid storage collision.
// bytes32(uint256(keccak256("ccip.pools.GHO.UpgradeableTokenPool.proxyPool")) - 1)
bytes32 internal constant PROXY_POOL_SLOT = 0x75bb68f1b335d4dab6963140ecff58281174ef4362bb85a8593ab9379f24fae2;

/// @dev The bridgeable token that is managed by this pool.
IERC20 internal immutable i_token;
/// @dev The address of the arm proxy
address internal immutable i_armProxy;
/// @dev The immutable flag that indicates if the pool is access-controlled.
bool internal immutable i_allowlistEnabled;
/// @dev A set of addresses allowed to trigger lockOrBurn as original senders.
/// Only takes effect if i_allowlistEnabled is true.
/// This can be used to ensure only token-issuer specified addresses can
/// move tokens.
EnumerableSet.AddressSet internal s_allowList;
/// @dev The address of the router
IRouter internal s_router;
/// @dev A set of allowed chain selectors. We want the allowlist to be enumerable to
/// be able to quickly determine (without parsing logs) who can access the pool.
/// @dev The chain selectors are in uin256 format because of the EnumerableSet implementation.
EnumerableSet.UintSet internal s_remoteChainSelectors;
/// @dev Outbound rate limits. Corresponds to the inbound rate limit for the pool
/// on the remote chain.
mapping(uint64 => RateLimiter.TokenBucket) internal s_outboundRateLimits;
/// @dev Inbound rate limits. This allows per destination chain
/// token issuer specified rate limiting (e.g. issuers may trust chains to varying
/// degrees and prefer different limits)
mapping(uint64 => RateLimiter.TokenBucket) internal s_inboundRateLimits;

constructor(IERC20 token, address armProxy, bool allowlistEnabled) {
if (address(token) == address(0)) revert ZeroAddressNotAllowed();
i_token = token;
i_armProxy = armProxy;
i_allowlistEnabled = allowlistEnabled;
}

/// @notice Get ARM proxy address
/// @return armProxy Address of arm proxy
function getArmProxy() public view returns (address armProxy) {
return i_armProxy;
}

function getToken() public view returns (IERC20 token) {
return i_token;
}

/// @notice Gets the pool's Router
/// @return router The pool's Router
function getRouter() public view returns (address router) {
return address(s_router);
}

/// @notice Sets the pool's Router
/// @param newRouter The new Router
function setRouter(
address newRouter
) public onlyOwner {
if (newRouter == address(0)) revert ZeroAddressNotAllowed();
address oldRouter = address(s_router);
s_router = IRouter(newRouter);

emit RouterUpdated(oldRouter, newRouter);
}

function supportsInterface(
bytes4 interfaceId
) public pure virtual override returns (bool) {
return interfaceId == type(IPoolV1).interfaceId || interfaceId == type(IERC165).interfaceId;
}

// ================================================================
// │ Chain permissions │
// ================================================================

/// @notice Checks whether a chain selector is permissioned on this contract.
/// @return true if the given chain selector is a permissioned remote chain.
function isSupportedChain(
uint64 remoteChainSelector
) public view returns (bool) {
return s_remoteChainSelectors.contains(remoteChainSelector);
}

/// @notice Get list of allowed chains
/// @return list of chains.
function getSupportedChains() public view returns (uint64[] memory) {
uint256[] memory uint256ChainSelectors = s_remoteChainSelectors.values();
uint64[] memory chainSelectors = new uint64[](uint256ChainSelectors.length);
for (uint256 i = 0; i < uint256ChainSelectors.length; ++i) {
chainSelectors[i] = uint64(uint256ChainSelectors[i]);
}

return chainSelectors;
}

/// @notice Sets the permissions for a list of chains selectors. Actual senders for these chains
/// need to be allowed on the Router to interact with this pool.
/// @dev Only callable by the owner
/// @param chains A list of chains and their new permission status & rate limits. Rate limits
/// are only used when the chain is being added through `allowed` being true.
function applyChainUpdates(
ChainUpdate[] calldata chains
) external virtual onlyOwner {
for (uint256 i = 0; i < chains.length; ++i) {
ChainUpdate memory update = chains[i];
RateLimiter._validateTokenBucketConfig(update.outboundRateLimiterConfig, !update.allowed);
RateLimiter._validateTokenBucketConfig(update.inboundRateLimiterConfig, !update.allowed);

if (update.allowed) {
// If the chain already exists, revert
if (!s_remoteChainSelectors.add(update.remoteChainSelector)) {
revert ChainAlreadyExists(update.remoteChainSelector);
}

s_outboundRateLimits[update.remoteChainSelector] = RateLimiter.TokenBucket({
rate: update.outboundRateLimiterConfig.rate,
capacity: update.outboundRateLimiterConfig.capacity,
tokens: update.outboundRateLimiterConfig.capacity,
lastUpdated: uint32(block.timestamp),
isEnabled: update.outboundRateLimiterConfig.isEnabled
});

s_inboundRateLimits[update.remoteChainSelector] = RateLimiter.TokenBucket({
rate: update.inboundRateLimiterConfig.rate,
capacity: update.inboundRateLimiterConfig.capacity,
tokens: update.inboundRateLimiterConfig.capacity,
lastUpdated: uint32(block.timestamp),
isEnabled: update.inboundRateLimiterConfig.isEnabled
});
emit ChainAdded(update.remoteChainSelector, update.outboundRateLimiterConfig, update.inboundRateLimiterConfig);
} else {
// If the chain doesn't exist, revert
if (!s_remoteChainSelectors.remove(update.remoteChainSelector)) {
revert NonExistentChain(update.remoteChainSelector);
}

delete s_inboundRateLimits[update.remoteChainSelector];
delete s_outboundRateLimits[update.remoteChainSelector];
emit ChainRemoved(update.remoteChainSelector);
}
}
}

// ================================================================
// │ Rate limiting │
// ================================================================

/// @notice Consumes outbound rate limiting capacity in this pool
function _consumeOutboundRateLimit(uint64 remoteChainSelector, uint256 amount) internal {
s_outboundRateLimits[remoteChainSelector]._consume(amount, address(i_token));
}

/// @notice Consumes inbound rate limiting capacity in this pool
function _consumeInboundRateLimit(uint64 remoteChainSelector, uint256 amount) internal {
s_inboundRateLimits[remoteChainSelector]._consume(amount, address(i_token));
}

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

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

/// @notice Sets the chain rate limiter config.
/// @param remoteChainSelector The remote chain selector for which the rate limits apply.
/// @param outboundConfig The new outbound rate limiter config, meaning the onRamp rate limits for the given chain.
/// @param inboundConfig The new inbound rate limiter config, meaning the offRamp rate limits for the given chain.
function setChainRateLimiterConfig(
uint64 remoteChainSelector,
RateLimiter.Config memory outboundConfig,
RateLimiter.Config memory inboundConfig
) external virtual onlyOwner {
_setRateLimitConfig(remoteChainSelector, outboundConfig, inboundConfig);
}

function _setRateLimitConfig(
uint64 remoteChainSelector,
RateLimiter.Config memory outboundConfig,
RateLimiter.Config memory inboundConfig
) internal {
if (!isSupportedChain(remoteChainSelector)) revert NonExistentChain(remoteChainSelector);
RateLimiter._validateTokenBucketConfig(outboundConfig, false);
s_outboundRateLimits[remoteChainSelector]._setTokenBucketConfig(outboundConfig);
RateLimiter._validateTokenBucketConfig(inboundConfig, false);
s_inboundRateLimits[remoteChainSelector]._setTokenBucketConfig(inboundConfig);
emit ChainConfigured(remoteChainSelector, outboundConfig, inboundConfig);
}

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

/// @notice Checks whether remote chain selector is configured on this contract, and if the msg.sender
/// is a permissioned onRamp for the given chain on the Router.
modifier onlyOnRamp(
uint64 remoteChainSelector
) {
if (!isSupportedChain(remoteChainSelector)) revert ChainNotAllowed(remoteChainSelector);
if (!(msg.sender == getProxyPool() || msg.sender == s_router.getOnRamp(remoteChainSelector))) {
revert CallerIsNotARampOnRouter(msg.sender);
}
_;
}

/// @notice Checks whether remote chain selector is configured on this contract, and if the msg.sender
/// is a permissioned offRamp for the given chain on the Router.
modifier onlyOffRamp(
uint64 remoteChainSelector
) {
if (!isSupportedChain(remoteChainSelector)) revert ChainNotAllowed(remoteChainSelector);
if (!(msg.sender == getProxyPool() || s_router.isOffRamp(remoteChainSelector, msg.sender))) {
revert CallerIsNotARampOnRouter(msg.sender);
}
_;
}

// ================================================================
// │ Allowlist │
// ================================================================

modifier checkAllowList(
address sender
) {
if (i_allowlistEnabled && !s_allowList.contains(sender)) revert SenderNotAllowed(sender);
_;
}

/// @notice Gets whether the allowList functionality is enabled.
/// @return true is enabled, false if not.
function getAllowListEnabled() external view returns (bool) {
return i_allowlistEnabled;
}

/// @notice Gets the allowed addresses.
/// @return The allowed addresses.
function getAllowList() external view returns (address[] memory) {
return s_allowList.values();
}

/// @notice Apply updates to the allow list.
/// @param removes The addresses to be removed.
/// @param adds The addresses to be added.
/// @dev allowListing will be removed before public launch
function applyAllowListUpdates(address[] calldata removes, address[] calldata adds) external onlyOwner {
_applyAllowListUpdates(removes, adds);
}

/// @notice Internal version of applyAllowListUpdates to allow for reuse in the constructor.
function _applyAllowListUpdates(address[] memory removes, address[] memory adds) internal {
if (!i_allowlistEnabled) revert AllowListNotEnabled();

for (uint256 i = 0; i < removes.length; ++i) {
address toRemove = removes[i];
if (s_allowList.remove(toRemove)) {
emit AllowListRemove(toRemove);
}
}
for (uint256 i = 0; i < adds.length; ++i) {
address toAdd = adds[i];
if (toAdd == address(0)) {
continue;
}
if (s_allowList.add(toAdd)) {
emit AllowListAdd(toAdd);
}
}
}

/// @notice Ensure that there is no active curse.
modifier whenHealthy() {
if (IRMN(i_armProxy).isCursed()) revert BadARMSignal();
_;
}

/// @notice Getter for proxy pool address.
/// @return proxyPool The proxy pool address.
function getProxyPool() public view returns (address proxyPool) {
assembly ("memory-safe") {
proxyPool := shr(96, shl(96, sload(PROXY_POOL_SLOT)))
}
}

/// @notice Setter for proxy pool address, only callable by the DAO.
/// @param proxyPool The address of the proxy pool.
function setProxyPool(
address proxyPool
) external onlyOwner {
if (proxyPool == address(0)) revert ZeroAddressNotAllowed();
assembly ("memory-safe") {
sstore(PROXY_POOL_SLOT, proxyPool)
}
}
}
Loading

0 comments on commit 0bd1198

Please sign in to comment.