-
Notifications
You must be signed in to change notification settings - Fork 16
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
OWR and OWRfactory audit fix #85
Changes from all commits
473abf1
8093970
c53a831
f64b72d
0a8eceb
490da94
6d3dd94
a3b8e5f
be13b7d
3454c91
fa5ad33
012a543
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,10 +9,8 @@ import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol"; | |
/// @author Obol | ||
/// @notice A maximally-composable contract that distributes payments | ||
/// based on threshold to it's recipients | ||
/// @dev Only one token can be distributed for a given deployment. There is a | ||
/// recovery method for non-target tokens sent by accident. | ||
/// Target ERC20s with very large decimals may overflow & cause issues. | ||
/// This contract uses token = address(0) to refer to ETH. | ||
/// @dev Only ETH can be distributed for a given deployment. There is a | ||
/// recovery method for tokens sent by accident. | ||
contract OptimisticWithdrawalRecipient is Clone { | ||
/// ----------------------------------------------------------------------- | ||
/// libraries | ||
|
@@ -24,9 +22,6 @@ contract OptimisticWithdrawalRecipient is Clone { | |
/// errors | ||
/// ----------------------------------------------------------------------- | ||
|
||
/// Invalid token recovery; cannot recover the OWRecipient token | ||
error InvalidTokenRecovery_OWRToken(); | ||
|
||
/// Invalid token recovery recipient | ||
error InvalidTokenRecovery_InvalidRecipient(); | ||
|
||
|
@@ -49,9 +44,9 @@ contract OptimisticWithdrawalRecipient is Clone { | |
/// pulling | ||
event DistributeFunds(uint256 principalPayout, uint256 rewardPayout, uint256 pullFlowFlag); | ||
|
||
/// Emitted after non-OWRecipient tokens are recovered to a recipient | ||
/// Emitted after tokens are recovered to a recipient | ||
/// @param recoveryAddressToken Recovered token (cannot be | ||
/// OptimisticWithdrawalRecipient token) | ||
/// ETH) | ||
/// @param recipient Address receiving recovered token | ||
/// @param amount Amount of recovered token | ||
event RecoverNonOWRecipientFunds(address recoveryAddressToken, address recipient, uint256 amount); | ||
|
@@ -69,8 +64,6 @@ contract OptimisticWithdrawalRecipient is Clone { | |
/// storage - constants | ||
/// ----------------------------------------------------------------------- | ||
|
||
address internal constant ETH_ADDRESS = address(0); | ||
|
||
uint256 internal constant PUSH = 0; | ||
uint256 internal constant PULL = 1; | ||
|
||
|
@@ -86,22 +79,14 @@ contract OptimisticWithdrawalRecipient is Clone { | |
/// storage - cwia offsets | ||
/// ----------------------------------------------------------------------- | ||
|
||
// token (address, 20 bytes), recoveryAddress (address, 20 bytes), | ||
// recoveryAddress (address, 20 bytes), | ||
// tranches (uint256[], numTranches * 32 bytes) | ||
|
||
// 0; first item | ||
uint256 internal constant TOKEN_OFFSET = 0; | ||
// 20 = token_offset (0) + token_size (address, 20 bytes) | ||
uint256 internal constant RECOVERY_ADDRESS_OFFSET = 20; | ||
// 40 = recoveryAddress_offset (20) + recoveryAddress_size (address, 20 | ||
uint256 internal constant RECOVERY_ADDRESS_OFFSET = 0; | ||
// 20 = recoveryAddress_offset (0) + recoveryAddress_size (address, 20 | ||
// bytes) | ||
uint256 internal constant TRANCHES_OFFSET = 40; | ||
|
||
/// Address of ERC20 to distribute (0x0 used for ETH) | ||
/// @dev equivalent to address public immutable token; | ||
function token() public pure returns (address) { | ||
return _getArgAddress(TOKEN_OFFSET); | ||
} | ||
uint256 internal constant TRANCHES_OFFSET = 20; | ||
|
||
/// Address to recover non-OWR tokens to | ||
/// @dev equivalent to address public immutable recoveryAddress; | ||
|
@@ -122,10 +107,6 @@ contract OptimisticWithdrawalRecipient is Clone { | |
/// storage - mutables | ||
/// ----------------------------------------------------------------------- | ||
|
||
/// Amount of distributed OWRecipient token | ||
/// @dev ERC20s with very large decimals may overflow & cause issues | ||
uint128 public distributedFunds; | ||
|
||
/// Amount of active balance set aside for pulls | ||
/// @dev ERC20s with very large decimals may overflow & cause issues | ||
uint128 public fundsPendingWithdrawal; | ||
|
@@ -179,9 +160,6 @@ contract OptimisticWithdrawalRecipient is Clone { | |
function recoverFunds(address nonOWRToken, address recipient) external payable { | ||
/// checks | ||
|
||
// revert if caller tries to recover OWRecipient token | ||
if (nonOWRToken == token()) revert InvalidTokenRecovery_OWRToken(); | ||
|
||
// if recoveryAddress is set, recipient must match it | ||
// else, recipient must be one of the OWR recipients | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it a little weird that there is subjectivity around who gets the recovered token, i.e. if you're an operator, and you see an airdrop has happened, you can rush in and recover it to the reward address, netting you X%. Whereas the beneficial owner would have swept it to the principal address, keeping 100% otherwise. Should we consider that recovery can only be to either principal or a recovery address and not (principal || reward) or recovery? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looking at your tests, I see that you just set |
||
|
||
|
@@ -201,30 +179,22 @@ contract OptimisticWithdrawalRecipient is Clone { | |
/// interactions | ||
|
||
// recover non-target token | ||
uint256 amount; | ||
if (nonOWRToken == ETH_ADDRESS) { | ||
amount = address(this).balance; | ||
recipient.safeTransferETH(amount); | ||
} else { | ||
amount = ERC20(nonOWRToken).balanceOf(address(this)); | ||
nonOWRToken.safeTransfer(recipient, amount); | ||
} | ||
uint256 amount = ERC20(nonOWRToken).balanceOf(address(this)); | ||
nonOWRToken.safeTransfer(recipient, amount); | ||
|
||
emit RecoverNonOWRecipientFunds(nonOWRToken, recipient, amount); | ||
} | ||
|
||
/// Withdraw token balance for account `account` | ||
/// @param account Address to withdraw on behalf of | ||
function withdraw(address account) external { | ||
address _token = token(); | ||
uint256 tokenAmount = pullBalances[account]; | ||
unchecked { | ||
// shouldn't underflow; fundsPendingWithdrawal = sum(pullBalances) | ||
fundsPendingWithdrawal -= uint128(tokenAmount); | ||
} | ||
pullBalances[account] = 0; | ||
if (_token == ETH_ADDRESS) account.safeTransferETH(tokenAmount); | ||
else _token.safeTransfer(account, tokenAmount); | ||
account.safeTransferETH(tokenAmount); | ||
|
||
emit Withdrawal(account, tokenAmount); | ||
} | ||
|
@@ -268,23 +238,12 @@ contract OptimisticWithdrawalRecipient is Clone { | |
/// effects | ||
|
||
// load storage into memory | ||
// fetch the token we want to distribute | ||
address _token = token(); | ||
// the amount of funds distributed so far | ||
uint256 _startingDistributedFunds = uint256(distributedFunds); | ||
uint256 _endingDistributedFunds; | ||
uint256 currentbalance = address(this).balance; | ||
uint256 _fundsToBeDistributed; | ||
uint256 _claimedPrincipalFunds = uint256(claimedPrincipalFunds); | ||
uint256 _memoryFundsPendingWithdrawal = uint256(fundsPendingWithdrawal); | ||
unchecked { | ||
// shouldn't overflow | ||
_endingDistributedFunds = _startingDistributedFunds | ||
// fundsPendingWithdrawal is always <= _startingDistributedFunds | ||
- _memoryFundsPendingWithdrawal | ||
// recognizes 0x0 as ETH | ||
// shouldn't need to worry about re-entrancy from ERC20 view fn | ||
+ (_token == ETH_ADDRESS ? address(this).balance : ERC20(_token).balanceOf(address(this))); | ||
_fundsToBeDistributed = _endingDistributedFunds - _startingDistributedFunds; | ||
_fundsToBeDistributed = currentbalance - _memoryFundsPendingWithdrawal; | ||
} | ||
|
||
(address principalRecipient, address rewardRecipient, uint256 amountOfPrincipalStake) = getTranches(); | ||
|
@@ -316,9 +275,8 @@ contract OptimisticWithdrawalRecipient is Clone { | |
} | ||
|
||
{ | ||
if (_endingDistributedFunds > type(uint128).max) revert InvalidDistribution_TooLarge(); | ||
if (_fundsToBeDistributed > type(uint128).max) revert InvalidDistribution_TooLarge(); | ||
// Write to storage | ||
distributedFunds = uint128(_endingDistributedFunds); | ||
// the principal value | ||
claimedPrincipalFunds += _principalPayout; | ||
} | ||
|
@@ -331,9 +289,9 @@ contract OptimisticWithdrawalRecipient is Clone { | |
// when later external calls fail (bc balance is emptied early) | ||
|
||
// pay out principal | ||
_payout(_token, principalRecipient, _principalPayout, pullFlowFlag); | ||
_payout(principalRecipient, _principalPayout, pullFlowFlag); | ||
// pay out reward | ||
_payout(_token, rewardRecipient, _rewardPayout, pullFlowFlag); | ||
_payout(rewardRecipient, _rewardPayout, pullFlowFlag); | ||
|
||
if (pullFlowFlag == PULL) { | ||
if (_principalPayout > 0 || _rewardPayout > 0) { | ||
|
@@ -345,15 +303,13 @@ contract OptimisticWithdrawalRecipient is Clone { | |
emit DistributeFunds(_principalPayout, _rewardPayout, pullFlowFlag); | ||
} | ||
|
||
function _payout(address payoutToken, address recipient, uint256 payoutAmount, uint256 pullFlowFlag) internal { | ||
function _payout(address recipient, uint256 payoutAmount, uint256 pullFlowFlag) internal { | ||
if (payoutAmount > 0) { | ||
if (pullFlowFlag == PULL) { | ||
// Write to Storage | ||
pullBalances[recipient] += payoutAmount; | ||
} else if (payoutToken == ETH_ADDRESS) { | ||
recipient.safeTransferETH(payoutAmount); | ||
} else { | ||
payoutToken.safeTransfer(recipient, payoutAmount); | ||
recipient.safeTransferETH(payoutAmount); | ||
} | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume splitWallet will handle ERC20 transfers properly? Is this just a good practice change or any other reason why we would add this? Also i assume if it fails, the tokens are just stuck forever and 🤷, because there's not another place to recover to?