Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
60 changes: 54 additions & 6 deletions solidity/src/FlowYieldVaultsRequests.sol
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,9 @@ contract FlowYieldVaultsRequests is ReentrancyGuard, Ownable2Step {
/// @notice Claimable refunds from cancelled/dropped/failed requests: user => token => amount
mapping(address => mapping(address => uint256)) public claimableRefunds;

/// @notice Total balance of each token accounted for in contract (escrowed/claimable)
mapping(address => uint256) public totalAccountedBalance;

/// @notice All requests indexed by request ID
mapping(uint256 => Request) public requests;

Expand Down Expand Up @@ -282,6 +285,15 @@ contract FlowYieldVaultsRequests is ReentrancyGuard, Ownable2Step {
/// @notice No refund available for the specified token
error NoRefundAvailable(address token);

/// @notice Recovery user address cannot be zero
error InvalidRecoveryUserAddress();

/// @notice ERC20 token address cannot be the native $FLOW token
error InvalidRecoveryTokenAddress();

/// @notice The requested recovery amount exceeds the available excess amount
error InsufficientRecoveryAmount(uint256 available, uint256 requested);

// ============================================
// Events
// ============================================
Expand Down Expand Up @@ -458,6 +470,16 @@ contract FlowYieldVaultsRequests is ReentrancyGuard, Ownable2Step {
uint256 amount
);

/// @notice Emitted when the contract owner recovers user tokens
/// @param to User who received the tokens
/// @param tokenAddress ERC20 token claimed
/// @param amount Amount recovered
event TokensRecovered(
address indexed to,
address indexed tokenAddress,
uint256 amount
);

// ============================================
// Modifiers
// ============================================
Expand Down Expand Up @@ -514,15 +536,37 @@ contract FlowYieldVaultsRequests is ReentrancyGuard, Ownable2Step {
}

// ============================================
// Receive Function
// External Functions - Admin
// ============================================

/// @notice Allows contract to receive native $FLOW
receive() external payable {}
/// @notice Recovery mechanism for accidental user transfers of ERC20 tokens
/// @dev Tokens sent directly to the contract outside the intended request
/// flows (including accidental transfers, airdrops etc) are only
/// recoverable through this function here.
/// @param to The recipient address to transfer funds to.
/// @param tokenAddress The token to transfer.
/// @param amount The amount of tokens to transfer (in wei).
function recoverTokens(
address to,
address tokenAddress,
uint256 amount
) external onlyOwner nonReentrant {
if (to == address(0) || to == address(this))
revert InvalidRecoveryUserAddress();
if (isNativeFlow(tokenAddress)) revert InvalidRecoveryTokenAddress();
if (amount == 0) revert AmountMustBeGreaterThanZero();

// ============================================
// External Functions - Admin
// ============================================
uint256 contractBalance = IERC20(tokenAddress).balanceOf(address(this));
uint256 excess = contractBalance > totalAccountedBalance[tokenAddress]
? contractBalance - totalAccountedBalance[tokenAddress]
: 0;
if (amount > excess) {
revert InsufficientRecoveryAmount(excess, amount);
}

IERC20(tokenAddress).safeTransfer(to, amount);
emit TokensRecovered(to, tokenAddress, amount);
}

/// @notice Updates the authorized COA address
/// @param _coa New COA address
Expand Down Expand Up @@ -1001,6 +1045,7 @@ contract FlowYieldVaultsRequests is ReentrancyGuard, Ownable2Step {
address(this),
request.amount
);
totalAccountedBalance[request.tokenAddress] += request.amount;
}
// Credit refunded funds to claimable refunds for later claim
claimableRefunds[request.user][request.tokenAddress] += request
Expand Down Expand Up @@ -1505,6 +1550,7 @@ contract FlowYieldVaultsRequests is ReentrancyGuard, Ownable2Step {
authorizedCOA,
request.amount
);
totalAccountedBalance[request.tokenAddress] -= request.amount;
}
emit FundsWithdrawn(
authorizedCOA,
Expand Down Expand Up @@ -1566,6 +1612,7 @@ contract FlowYieldVaultsRequests is ReentrancyGuard, Ownable2Step {
address(this),
amount
);
totalAccountedBalance[tokenAddress] += amount;
}
}

Expand Down Expand Up @@ -1625,6 +1672,7 @@ contract FlowYieldVaultsRequests is ReentrancyGuard, Ownable2Step {
} else {
// ERC20: use SafeERC20 for safe token transfer
IERC20(tokenAddress).safeTransfer(to, amount);
totalAccountedBalance[tokenAddress] -= amount;
}
}

Expand Down
Loading
Loading