-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #41 from immutable/SMR-1677-FlowRate
SMR-1677 Flow Rate
- Loading branch information
Showing
14 changed files
with
2,296 additions
and
2,001 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
// SPDX-License-Identifier: Apache 2.0 | ||
pragma solidity 0.8.19; | ||
|
||
interface IFlowRateWithdrawalQueueEvents { | ||
// Indicates a withdrawal has been queued. | ||
event EnQueuedWithdrawal( | ||
address indexed token, | ||
address indexed withdrawer, | ||
address indexed receiver, | ||
uint256 amount, | ||
uint256 timestamp, | ||
uint256 index | ||
); | ||
|
||
// Indicates a withdrawal has been processed. | ||
event ProcessedWithdrawal( | ||
address indexed token, | ||
address indexed withdrawer, | ||
address indexed receiver, | ||
uint256 amount, | ||
uint256 timestamp, | ||
uint256 index | ||
); | ||
|
||
// Indicates the new withdrawal delay. | ||
event WithdrawalDelayUpdated(uint256 delay, uint256 previousDelay); | ||
} | ||
|
||
interface IFlowRateWithdrawalQueueErrors { | ||
// A withdrawal was being processed, but the index is outside of the array. | ||
error IndexOutsideWithdrawalQueue(uint256 lengthOfQueue, uint256 requestedIndex); | ||
|
||
// A withdrawal was being processed, but the withdrawal is not yet available. | ||
error WithdrawalRequestTooEarly(uint256 timeNow, uint256 currentWithdrawalTime); | ||
|
||
// A withdrawal was being processed, but the token is zero. This indicates that the | ||
// withdrawal has already been processed. | ||
error WithdrawalAlreadyProcessed(address receiver, uint256 index); | ||
|
||
// Attempting to enqueue a token with token address = 0. | ||
error TokenIsZero(address receiver); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
// SPDX-License-Identifier: Apache 2.0 | ||
pragma solidity 0.8.19; | ||
|
||
interface IRootERC20BridgeFlowRateEvents { | ||
/** | ||
* @notice Indicates rate control thresholds have been set for a certain token. | ||
* @param token The token thresholds applied to. | ||
* @param capacity The size of the bucket in tokens. | ||
* @param refillRate How quickly the bucket refills in tokens per second. | ||
* @param largeTransferThreshold Threshold over which a withdrawal is deemed to be large, | ||
* and will be put in the withdrawal queue. | ||
*/ | ||
event RateControlThresholdSet( | ||
address indexed token, | ||
uint256 capacity, | ||
uint256 refillRate, | ||
uint256 largeTransferThreshold, | ||
uint256 previousCapacity, | ||
uint256 previousRefillRate, | ||
uint256 previousLargeTransferThreshold | ||
); | ||
|
||
/** | ||
* @notice Indicates a withdrawal was queued. | ||
* @param token Address of token that is being withdrawn. | ||
* @param withdrawer Child chain sender of tokens. | ||
* @param receiver Recipient of tokens. | ||
* @param amount The number of tokens. | ||
* @param delayWithdrawalLargeAmount is true if the reason for queuing was a large transfer. | ||
* @param delayWithdrawalUnknownToken is true if the reason for queuing was that the | ||
* token had not been configured using the setRateControlThreshold function. | ||
* @param withdrawalQueueActivated is true if the withdrawal queue has been activated. | ||
*/ | ||
event QueuedWithdrawal( | ||
address indexed token, | ||
address indexed withdrawer, | ||
address indexed receiver, | ||
uint256 amount, | ||
bool delayWithdrawalLargeAmount, | ||
bool delayWithdrawalUnknownToken, | ||
bool withdrawalQueueActivated | ||
); | ||
} | ||
|
||
interface IRootERC20BridgeFlowRateErrors { | ||
// Error if the RootERC20Bridge initializer is called, and not the one for this contract. | ||
error WrongInitializer(); | ||
// finaliseQueuedWithdrawalsAggregated was called with a zero length indices array. | ||
error ProvideAtLeastOneIndex(); | ||
// The expected and actual token did not match for an aggregated withdrawal. | ||
error MixedTokens(address token, address actualToken); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
// Copyright Immutable Pty Ltd 2018 - 2023 | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.19; | ||
|
||
/** | ||
* @title Flow Rate Detection | ||
* @author Immutable Pty Ltd (Peter Robinson @drinkcoffee and Zhenyang Shi @wcgcyx) | ||
* @notice Detects large flows of tokens using a bucket system. | ||
* @dev Each token has a bucket. The bucket is filled at a constant rate: a number of | ||
* tokens per second. The bucket empties each time there is a withdrawal. Withdrawal | ||
* requests for tokens that don't have a configured bucket are delayed. | ||
* Note: This code is part of RootERC20BridgeFlowRate. It has been separated out | ||
* to make it easier to understand and test the functionality. | ||
* Note that this contract is upgradeable. | ||
*/ | ||
abstract contract FlowRateDetection { | ||
// Holds flow rate information for a single token. | ||
struct Bucket { | ||
// The number of tokens that can fit in the bucket. | ||
// A capacity of zero indicates that flow rate detection is not configured for the token. | ||
uint256 capacity; | ||
// The number of tokens in the bucket. | ||
uint256 depth; | ||
// The last time the bucket was updated. | ||
uint256 refillTime; | ||
// The number of tokens added per second. | ||
uint256 refillRate; | ||
} | ||
|
||
// Map ERC 20 token address to buckets | ||
mapping(address => Bucket) public flowRateBuckets; | ||
|
||
// True if all tokens should be put in the withdrawal queue. | ||
bool public withdrawalQueueActivated; | ||
|
||
// Emitted when there is a withdrawal request for a token for which there is no bucket. | ||
event WithdrawalForNonFlowRatedToken(address indexed token, uint256 amount); | ||
// Emitted when queue activated or deactivated | ||
event AutoActivatedWithdrawalQueue(); | ||
event ActivatedWithdrawalQueue(address who); | ||
event DeactivatedWithdrawalQueue(address who); | ||
|
||
error InvalidToken(); | ||
error InvalidCapacity(); | ||
error InvalidRefillRate(); | ||
|
||
/** | ||
* @notice Activate the withdrawal queue for all tokens. | ||
*/ | ||
function _activateWithdrawalQueue() internal { | ||
withdrawalQueueActivated = true; | ||
emit ActivatedWithdrawalQueue(msg.sender); | ||
} | ||
|
||
/** | ||
* @notice Deactivate the withdrawal queue for all tokens. | ||
* @dev This does not affect withdrawals already in the queue. | ||
*/ | ||
function _deactivateWithdrawalQueue() internal { | ||
withdrawalQueueActivated = false; | ||
emit DeactivatedWithdrawalQueue(msg.sender); | ||
} | ||
|
||
/** | ||
* @notice Initialise or update a bucket for a token. | ||
* @param token Address of the token to configure the bucket for. | ||
* @param capacity The number of tokens before the bucket overflows. | ||
* @param refillRate The number of tokens added to the bucket each second. | ||
* @dev If this is a new bucket, then the depth is the capacity. If the bucket is existing, then | ||
* the depth is not altered. | ||
*/ | ||
function _setFlowRateThreshold(address token, uint256 capacity, uint256 refillRate) internal { | ||
if (token == address(0)) { | ||
revert InvalidToken(); | ||
} | ||
if (capacity == 0) { | ||
revert InvalidCapacity(); | ||
} | ||
if (refillRate == 0) { | ||
revert InvalidRefillRate(); | ||
} | ||
Bucket storage bucket = flowRateBuckets[token]; | ||
if (bucket.capacity == 0) { | ||
bucket.depth = capacity; | ||
} | ||
bucket.capacity = capacity; | ||
bucket.refillRate = refillRate; | ||
} | ||
|
||
/** | ||
* @notice Update the flow rate measurement for a token. | ||
* @param token Address of token being withdrawn. | ||
* @param amount The number of tokens being withdrawn. | ||
* @return delayWithdrawal Delay this withdrawal because it is for an unconfigured token. | ||
*/ | ||
function _updateFlowRateBucket(address token, uint256 amount) internal returns (bool delayWithdrawal) { | ||
Bucket storage bucket = flowRateBuckets[token]; | ||
|
||
uint256 capacity = bucket.capacity; | ||
if (capacity == 0) { | ||
emit WithdrawalForNonFlowRatedToken(token, amount); | ||
return true; | ||
} | ||
|
||
// Calculate the depth assuming no withdrawal. | ||
// slither-disable-next-line timestamp | ||
uint256 depth = bucket.depth + (block.timestamp - bucket.refillTime) * bucket.refillRate; | ||
// slither-disable-next-line timestamp | ||
bucket.refillTime = block.timestamp; | ||
// slither-disable-next-line timestamp | ||
if (depth > capacity) { | ||
depth = capacity; | ||
} | ||
|
||
// slither-disable-next-line timestamp | ||
if (amount >= depth) { | ||
// The bucket is empty indicating the flow rate is high. Automatically | ||
// enable the withdrawal queue. | ||
emit AutoActivatedWithdrawalQueue(); | ||
withdrawalQueueActivated = true; | ||
bucket.depth = 0; | ||
} else { | ||
bucket.depth = depth - amount; | ||
} | ||
return false; | ||
} | ||
|
||
// slither-disable-next-line unused-state,naming-convention | ||
uint256[50] private __gapFlowRateDetecton; | ||
} |
Oops, something went wrong.