Skip to content

Commit

Permalink
More flexible getAvailableFunds (#698)
Browse files Browse the repository at this point in the history
* attack

* getAvailableFunds with bounded gas consumption

* token list getters

* Review changes

---------

Co-authored-by: Mischa <[email protected]>
Co-authored-by: Ana Julia <[email protected]>
  • Loading branch information
3 people authored Jul 7, 2023
1 parent 8658b7a commit 02a4d2f
Show file tree
Hide file tree
Showing 8 changed files with 691 additions and 911 deletions.
2 changes: 1 addition & 1 deletion contracts/domain/BosonConstants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ string constant BUNDLE_REQUIRES_AT_LEAST_ONE_TWIN_AND_ONE_OFFER = "Bundle must h
// Revert Reasons: Funds related
string constant NATIVE_WRONG_ADDRESS = "Native token address must be 0";
string constant NATIVE_WRONG_AMOUNT = "Transferred value must match amount";
string constant TOKEN_NAME_UNSPECIFIED = "Token name unspecified";
string constant TOKEN_NAME_UNSPECIFIED = "Token name unavailable";
string constant NATIVE_CURRENCY = "Native currency";
string constant TOKEN_AMOUNT_MISMATCH = "Number of amounts should match number of tokens";
string constant NOTHING_TO_WITHDRAW = "Nothing to withdraw";
Expand Down
36 changes: 35 additions & 1 deletion contracts/interfaces/handlers/IBosonFundsHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { IBosonFundsLibEvents } from "../events/IBosonFundsEvents.sol";
*
* @notice Handles custody and withdrawal of buyer and seller funds within the protocol.
*
* The ERC-165 identifier for this interface is: 0xb5850c2a
* The ERC-165 identifier for this interface is: 0x2f4a64d7
*/
interface IBosonFundsHandler is IBosonFundsEvents, IBosonFundsLibEvents {
/**
Expand Down Expand Up @@ -72,8 +72,42 @@ interface IBosonFundsHandler is IBosonFundsEvents, IBosonFundsLibEvents {
*/
function withdrawProtocolFees(address[] calldata _tokenList, uint256[] calldata _tokenAmounts) external;

/**
* @notice Returns list of addresses for which the entity has funds available.
* If the list is too long, it can be retrieved in chunks by using `getTokenListPaginated` and specifying _limit and _offset.
*
* @param _entityId - id of entity for which availability of funds should be checked
* @return tokenList - list of token addresses
*/
function getTokenList(uint256 _entityId) external view returns (address[] memory tokenList);

/**
* @notice Returns list of addresses for which the entity has funds available.
*
* @param _entityId - id of entity for which availability of funds should be checked
* @param _limit - the maximum number of token addresses that should be returned starting from the index defined by `_offset`. If `_offset` + `_limit` exceeds total tokens, `_limit` is adjusted to return all remaining tokens.
* @param _offset - the starting index from which to return token addresses. If `_offset` is greater than or equal to total tokens, an empty list is returned.
* @return tokenList - list of token addresses
*/
function getTokenListPaginated(
uint256 _entityId,
uint256 _limit,
uint256 _offset
) external view returns (address[] memory tokenList);

/**
* @notice Returns the information about the funds that an entity can use as a sellerDeposit and/or withdraw from the protocol.
* It tries to get information about all tokens that the entity has in availableFunds storage.
* If the token list is too long, this call may run out of gas. In this case, the caller should use the function `getAvailableFunds` and pass the token list.
*
* @param _entityId - id of entity for which availability of funds should be checked
* @return availableFunds - list of token addresses, token names and amount that can be used as a seller deposit or be withdrawn
*/
function getAllAvailableFunds(uint256 _entityId) external view returns (BosonTypes.Funds[] memory availableFunds);

/**
* @notice Returns the information about the funds that an entity can use as a sellerDeposit and/or withdraw from the protocol.
* To get a list of tokens that the entity has in availableFunds storage, use the function `getTokenList`.
*
* @param _entityId - id of entity for which availability of funds should be checked
* @param _tokenList - list of tokens addresses to get available funds
Expand Down
20 changes: 20 additions & 0 deletions contracts/mock/Foreign20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,25 @@ contract Foreign20Malicious2 is Foreign20 {
}
}

/**
* @title Foreign20 that consumes all gas when name called
*
*
* @notice Mock ERC-(20) for Unit Testing
*/
contract Foreign20MaliciousName is Foreign20 {
function name() public pure override returns (string memory) {
// name consumes all gas
unchecked {
uint256 i = 0;
while (true) {
i++;
}
}
return "nothing";
}
}

/**
* @title Foreign20 that takes a fee during the transfer
*
Expand Down Expand Up @@ -224,5 +243,6 @@ contract Foreign20GasTheft is Foreign20 {
while (true) {
// consume all gas
}
return false;
}
}
69 changes: 61 additions & 8 deletions contracts/protocol/facets/FundsHandlerFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -149,24 +149,77 @@ contract FundsHandlerFacet is IBosonFundsHandler, ProtocolBase {
withdrawFundsInternal(protocolAddresses().treasury, 0, _tokenList, _tokenAmounts);
}

/**
* @notice Returns list of addresses for which the entity has funds available.
* If the list is too long, it can be retrieved in chunks by using `getTokenListPaginated` and specifying _limit and _offset.
*
* @param _entityId - id of entity for which availability of funds should be checked
* @return tokenList - list of token addresses
*/
function getTokenList(uint256 _entityId) external view override returns (address[] memory tokenList) {
return protocolLookups().tokenList[_entityId];
}

/**
* @notice Returns list of addresses for which the entity has funds available.
*
* @param _entityId - id of entity for which availability of funds should be checked
* @param _limit - the maximum number of token addresses that should be returned starting from the index defined by `_offset`. If `_offset` + `_limit` exceeds total tokens, `_limit` is adjusted to return all remaining tokens.
* @param _offset - the starting index from which to return token addresses. If `_offset` is greater than or equal to total tokens, an empty list is returned.
* @return tokenList - list of token addresses
*/
function getTokenListPaginated(
uint256 _entityId,
uint256 _limit,
uint256 _offset
) external view override returns (address[] memory tokenList) {
address[] storage tokens = protocolLookups().tokenList[_entityId];

if (_offset >= tokens.length) {
return new address[](0);
} else if (_offset + _limit > tokens.length) {
_limit = tokens.length - _offset;
}

tokenList = new address[](_limit);

for (uint i = 0; i < _limit; i++) {
tokenList[i] = tokens[_offset++];
}

return tokenList;
}

/**
* @notice Returns the information about the funds that an entity can use as a sellerDeposit and/or withdraw from the protocol.
* It tries to get information about all tokens that the entity has in availableFunds storage.
* If the token list is too long, this call may run out of gas. In this case, the caller should use the function `getAvailableFunds` and pass the token list.
*
* @param _entityId - id of entity for which availability of funds should be checked
* @return availableFunds - list of token addresses, token names and amount that can be used as a seller deposit or be withdrawn
*/
function getAllAvailableFunds(uint256 _entityId) external view override returns (Funds[] memory availableFunds) {
// get list of token addresses for the entity
address[] memory tokenList = protocolLookups().tokenList[_entityId];
return getAvailableFunds(_entityId, tokenList);
}

/**
* @notice Returns the information about the funds that an entity can use as a sellerDeposit and/or withdraw from the protocol.
* To get a list of tokens that the entity has in availableFunds storage, use the function `getTokenList`.
*
* @param _entityId - id of entity for which availability of funds should be checked
* @param _tokenList - list of tokens addresses to get available funds
* @return availableFunds - list of token addresses, token names and amount that can be used as a seller deposit or be withdrawn
*/
function getAvailableFunds(
uint256 _entityId,
address[] calldata _tokenList
) external view override returns (Funds[] memory availableFunds) {
// Cache protocol lookups for reference
ProtocolLib.ProtocolLookups storage lookups = protocolLookups();

address[] memory _tokenList
) public view override returns (Funds[] memory availableFunds) {
availableFunds = new Funds[](_tokenList.length);

// Get entity's availableFunds storage pointer
mapping(address => uint256) storage entityFunds = lookups.availableFunds[_entityId];
mapping(address => uint256) storage entityFunds = protocolLookups().availableFunds[_entityId];

for (uint256 i = 0; i < _tokenList.length; i++) {
address tokenAddress = _tokenList[i];
Expand All @@ -176,8 +229,8 @@ contract FundsHandlerFacet is IBosonFundsHandler, ProtocolBase {
// If tokenAddress is 0, it represents the native currency
tokenName = NATIVE_CURRENCY;
} else {
// Try to get token name
try IERC20Metadata(tokenAddress).name() returns (string memory name) {
// Try to get token name. Typically, name consumes less than 30,000 gas, but we leave some extra gas just in case
try IERC20Metadata(tokenAddress).name{ gas: 40000 }() returns (string memory name) {
tokenName = name;
} catch {
tokenName = TOKEN_NAME_UNSPECIFIED;
Expand Down
Loading

0 comments on commit 02a4d2f

Please sign in to comment.