Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SOV-3057 wrapping all fees withdraw #517

Merged
merged 6 commits into from
Sep 16, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,4 @@ typechain/
*/deployments/*forked*
venv/
!/external/artifacts/
cache_hardhat/
206 changes: 102 additions & 104 deletions contracts/governance/FeeSharingCollector/FeeSharingCollector.sol
Original file line number Diff line number Diff line change
Expand Up @@ -347,30 +347,25 @@ contract FeeSharingCollector is

/// @notice Validates if the checkpoint is payable for the user
function validFromCheckpointsParam(
uint256[] memory _fromCheckpoints,
address[] memory _tokens,
TokenWithSkippedCheckpointsWithdraw[] memory _tokens,
address _user
) private view {
require(
_tokens.length == _fromCheckpoints.length,
"mismatch tokens and checkpoints length"
);

for (uint256 i = 0; i < _tokens.length; i++) {
TokenWithSkippedCheckpointsWithdraw memory tokenData = _tokens[i];
// _fromCheckpoint is checkpoint number, not array index, so should be > 1
require(_fromCheckpoints[i] > 1, "_fromCheckpoint param must be > 1");
uint256 fromCheckpointIndex = _fromCheckpoints[i] - 1;
require(tokenData.fromCheckpoint > 1, "_fromCheckpoint param must be > 1");
uint256 fromCheckpointIndex = tokenData.fromCheckpoint - 1;
require(
_fromCheckpoints[i] > processedCheckpoints[_user][_tokens[i]],
tokenData.fromCheckpoint > processedCheckpoints[_user][tokenData.tokenAddress],
"_fromCheckpoint param must be > userProcessedCheckpoints"
);
require(
_fromCheckpoints[i] <= totalTokenCheckpoints[_tokens[i]],
tokenData.fromCheckpoint <= totalTokenCheckpoints[tokenData.tokenAddress],
"_fromCheckpoint should be <= totalTokenCheckpoints"
);

Checkpoint memory prevCheckpoint =
tokenCheckpoints[_tokens[i]][fromCheckpointIndex - 1];
tokenCheckpoints[tokenData.tokenAddress][fromCheckpointIndex - 1];

uint96 weightedStake =
staking.getPriorWeightedStake(
Expand All @@ -383,7 +378,8 @@ contract FeeSharingCollector is
"User weighted stake should be zero at previous checkpoint"
);

Checkpoint memory fromCheckpoint = tokenCheckpoints[_tokens[i]][fromCheckpointIndex];
Checkpoint memory fromCheckpoint =
tokenCheckpoints[tokenData.tokenAddress][fromCheckpointIndex];
weightedStake = staking.getPriorWeightedStake(
_user,
fromCheckpoint.blockNumber - 1,
Expand Down Expand Up @@ -422,19 +418,20 @@ contract FeeSharingCollector is
*
* @dev WARNING! This function skips all the checkpoints before '_fromCheckpoint' irreversibly, use with care
*
* @param _tokens RBTC dummy to fit into existing data structure or SOV. Former address of the pool token.
* @param _fromCheckpoints Skips all the checkpoints before '_fromCheckpoint'
* should be calculated offchain with getNextPositiveUserCheckpoint function
* @param _tokens Array of TokenWithSkippedCheckpointsWithdraw struct, which contains the token address, and fromCheckpoiint
* fromCheckpoints Skips all the checkpoints before '_fromCheckpoint'
* should be calculated offchain with getNextPositiveUserCheckpoint function
* @param _maxCheckpoints Maximum number of checkpoints to be processed.
* @param _receiver The receiver of tokens or msg.sender
*
* @return total processed checkpoints
* */
function withdrawStartingFromCheckpoints(
address[] calldata _tokens,
uint256[] calldata _fromCheckpoints,
function _withdrawStartingFromCheckpoints(
TokenWithSkippedCheckpointsWithdraw[] memory _tokens,
uint32 _maxCheckpoints,
address _receiver
) external nonReentrant {
validFromCheckpointsParam(_fromCheckpoints, _tokens, msg.sender);
) internal returns (uint256 totalProcessedCheckpoints) {
validFromCheckpointsParam(_tokens, msg.sender);

if (_receiver == ZERO_ADDRESS) {
_receiver = msg.sender;
Expand All @@ -446,40 +443,41 @@ contract FeeSharingCollector is
uint256 rbtcAmountToSend;

for (uint256 i = 0; i < _tokens.length; i++) {
TokenWithSkippedCheckpointsWithdraw memory tokenData = _tokens[i];
if (_maxCheckpoints == 0) break;
uint256 _fromCheckpoint = _fromCheckpoints[i];
address _token = _tokens[i];
uint256 endToken;
uint256 totalAmount;

uint256 previousProcessedUserCheckpoints = processedCheckpoints[msg.sender][_token];
uint256 previousProcessedUserCheckpoints =
processedCheckpoints[msg.sender][tokenData.tokenAddress];
uint256 startingCheckpoint =
_fromCheckpoint > previousProcessedUserCheckpoints
? _fromCheckpoint
tokenData.fromCheckpoint > previousProcessedUserCheckpoints
? tokenData.fromCheckpoint
: previousProcessedUserCheckpoints;

if (
_token == address(wrbtcToken) ||
_token == loanPoolTokenWRBTC ||
_token == RBTC_DUMMY_ADDRESS_FOR_CHECKPOINT
tokenData.tokenAddress == address(wrbtcToken) ||
tokenData.tokenAddress == loanPoolTokenWRBTC ||
tokenData.tokenAddress == RBTC_DUMMY_ADDRESS_FOR_CHECKPOINT
) {
(totalAmount, endToken) = _withdrawRbtcTokenStartingFromCheckpoint(
_token,
_fromCheckpoint,
tokenData.tokenAddress,
tokenData.fromCheckpoint,
_maxCheckpoints,
_receiver
);
rbtcAmountToSend = rbtcAmountToSend.add(totalAmount);
} else {
(, endToken) = _withdrawStartingFromCheckpoint(
_token,
_fromCheckpoint,
tokenData.tokenAddress,
tokenData.fromCheckpoint,
_maxCheckpoints,
_receiver
);
}

uint256 _previousUsedCheckpoint = endToken.sub(startingCheckpoint).add(1);
totalProcessedCheckpoints += _previousUsedCheckpoint;
_maxCheckpoints = safe32(
_maxCheckpoints - _previousUsedCheckpoint,
"FeeSharingCollector: maxCheckpoint iteration exceeds 32 bits"
Expand All @@ -495,6 +493,73 @@ contract FeeSharingCollector is
}
}

/**
* @dev Function to wrap:
* 1. regular withdrawal for both rbtc & non-rbtc token
* 2. skipped checkpoints withdrawal for both rbtc & non-rbtc token
*
* @param _nonRbtcTokensRegularWithdraw array of non-rbtc token address with no skipped checkpoints that will be withdrawn
* @param _rbtcTokensRegularWithdraw array of rbtc token address with no skipped checkpoints that will be withdrawn
* @param _tokensWithSkippedCheckpoints array of rbtc & non-rbtc TokenWithSkippedCheckpointsWithdraw struct, which has skipped checkpoints that will be withdrawn
*
*/
function claimAllCollectedFees(
address[] calldata _nonRbtcTokensRegularWithdraw,
address[] calldata _rbtcTokensRegularWithdraw,
TokenWithSkippedCheckpointsWithdraw[] calldata _tokensWithSkippedCheckpoints,
uint32 _maxCheckpoints,
address _receiver
) external nonReentrant {
/** Process token with skipped checkpoints withdrawal */
uint256 totalProcessedCheckpoints;
if (_tokensWithSkippedCheckpoints.length > 0) {
totalProcessedCheckpoints = _withdrawStartingFromCheckpoints(
_tokensWithSkippedCheckpoints,
_maxCheckpoints,
_receiver
);
_maxCheckpoints = safe32(
_maxCheckpoints - totalProcessedCheckpoints,
"FeeSharingCollector: maxCheckpoint iteration exceeds 32 bits"
);
}

/** Process normal multiple withdrawal for RBTC based tokens */
if (_rbtcTokensRegularWithdraw.length > 0) {
totalProcessedCheckpoints = _withdrawRbtcTokens(
_rbtcTokensRegularWithdraw,
_maxCheckpoints,
_receiver
);
_maxCheckpoints = safe32(
_maxCheckpoints - totalProcessedCheckpoints,
"FeeSharingCollector: maxCheckpoint iteration exceeds 32 bits"
);
}

/** Process normal non-rbtc token withdrawal */
for (uint256 i = 0; i < _nonRbtcTokensRegularWithdraw.length; i++) {
uint256 endTokenCheckpoint;

address _nonRbtcTokenAddress = _nonRbtcTokensRegularWithdraw[i];

/** starting checkpoint is the previous processedCheckpoints for token */
uint256 startingCheckpoint = processedCheckpoints[msg.sender][_nonRbtcTokenAddress];

(, endTokenCheckpoint) = _withdraw(_nonRbtcTokenAddress, _maxCheckpoints, _receiver);

uint256 _previousUsedCheckpoint = endTokenCheckpoint.sub(startingCheckpoint);
if (startingCheckpoint > 0) {
_previousUsedCheckpoint.add(1);
}

_maxCheckpoints = safe32(
_maxCheckpoints - _previousUsedCheckpoint,
"FeeSharingCollector: maxCheckpoint iteration exceeds 32 bits"
);
}
}

function _withdrawStartingFromCheckpoint(
address _token,
uint256 _fromCheckpoint,
Expand Down Expand Up @@ -552,11 +617,11 @@ contract FeeSharingCollector is
* @param _maxCheckpoints Maximum number of checkpoints to be processed to workaround block gas limit
* @param _receiver An optional tokens receiver (msg.sender used if 0)
*/
function withdrawRbtcTokens(
address[] calldata _tokens,
function _withdrawRbtcTokens(
address[] memory _tokens,
uint32 _maxCheckpoints,
address _receiver
) external nonReentrant {
) internal returns (uint256 totalProcessedCheckpoints) {
validRBTCBasedTokens(_tokens);

if (_receiver == ZERO_ADDRESS) {
Expand All @@ -579,74 +644,7 @@ contract FeeSharingCollector is
// we only need to add used checkpoint by 1 only if starting checkpoint > 0
_previousUsedCheckpoint.add(1);
}
_maxCheckpoints = safe32(
_maxCheckpoints - _previousUsedCheckpoint,
"FeeSharingCollector: maxCheckpoint iteration exceeds 32 bits"
);
}

// send all rbtc
if (rbtcAmountToSend > 0) {
(bool success, ) = _receiver.call.value(rbtcAmountToSend)("");
require(success, "FeeSharingCollector::withdrawRBTC: Withdrawal failed");

emit RBTCWithdrawn(msg.sender, _receiver, rbtcAmountToSend);
}
}

/**
* @dev Withdraw all of the RBTC balance based starting from a specific checkpoint
* The function was designed to skip checkpoints with no fees for users
*
* This function will withdraw RBTC balance consists of:
* - rbtc balance
* - wrbtc balance which will be unwrapped to rbtc
* - iwrbtc balance which will be unwrapped to rbtc
*
* @dev WARNING! This function skips all the checkpoints before '_fromCheckpoint' irreversibly, use with care
*
* @param _tokens array of rbtc token to be withdrawn
* @param _fromCheckpoints Skips all the checkpoints before array of '_fromCheckpoint'
* should be calculated offchain with getNextPositiveUserCheckpoint function
* @param _maxCheckpoints Maximum number of checkpoints to be processed to workaround block gas limit
* @param _receiver An optional tokens receiver (msg.sender used if 0)
*/
function withdrawRbtcTokensStartingFromCheckpoint(
address[] calldata _tokens,
uint256[] calldata _fromCheckpoints,
uint32 _maxCheckpoints,
address _receiver
) external nonReentrant {
validFromCheckpointsParam(_fromCheckpoints, _tokens, msg.sender);
validRBTCBasedTokens(_tokens);

if (_receiver == ZERO_ADDRESS) {
_receiver = msg.sender;
}

uint256 rbtcAmountToSend;

for (uint256 i = 0; i < _tokens.length; i++) {
if (_maxCheckpoints == 0) break;
uint256 _fromCheckpoint = _fromCheckpoints[i];
address _token = _tokens[i];

uint256 previousProcessedUserCheckpoints = processedCheckpoints[msg.sender][_token];
uint256 startingCheckpoint =
_fromCheckpoint > previousProcessedUserCheckpoints
? _fromCheckpoint
: previousProcessedUserCheckpoints;

(uint256 totalAmount, uint256 endToken) =
_withdrawRbtcTokenStartingFromCheckpoint(
_token,
_fromCheckpoint,
_maxCheckpoints,
_receiver
);
rbtcAmountToSend = rbtcAmountToSend.add(totalAmount);

uint256 _previousUsedCheckpoint = endToken.sub(startingCheckpoint).add(1);
totalProcessedCheckpoints += _previousUsedCheckpoint;
_maxCheckpoints = safe32(
_maxCheckpoints - _previousUsedCheckpoint,
"FeeSharingCollector: maxCheckpoint iteration exceeds 32 bits"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ contract FeeSharingCollectorStorage is Ownable {
uint96 numTokens;
}

struct TokenWithSkippedCheckpointsWithdraw {
address tokenAddress;
uint256 fromCheckpoint;
}

/**
* @dev Add extra modifier (Reentrancy) below.
* Because we cannot add any additional storage slot before this storage contract after initial deployment
Expand Down
1 change: 1 addition & 0 deletions contracts/mockup/FeeSharingCollectorMockup.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pragma solidity ^0.5.17;
pragma experimental ABIEncoderV2;

import "../governance/FeeSharingCollector/FeeSharingCollector.sol";

Expand Down
Loading