Omniscia Teahouse Finance Audit
TeaVaultV3Portfolio Code Style Findings
TeaVaultV3Portfolio Code Style Findings
TVV-01C: Deprecated Usage of Signature Value Literals
Type | Severity | Location |
---|---|---|
Code Style | TeaVaultV3Portfolio.sol:L721 |
Description:
The referenced value literals represent function signatures which is a deprecated code style.
Example:
717// 0xc04b8d59: bytes4(keccak256( "exactInput((bytes,address,uint256,uint256,uint256))" ))718// 0xf28c0498: bytes4(keccak256( "exactOutput((bytes,address,uint256,uint256,uint256))" ))719abi.encodeWithSelector(720 TeaVaultV3Portfolio.simulateSwapViaV3RouterInternal.selector,721 bytes4(uint32(_isExactInput ? 0xc04b8d59 : 0xf28c0498)),722 _uniswapV3SwapRouter,723 params724)
Recommendation:
While the value literals have been confirmed as correct, we advise the Teahouse Finance team to import the relevant interfaces (i.e. ISwapRouter
) and use the selector
syntax (i.e. ISwapRouter.exactInput.selector
instead of 0xc04b8d59
).
Alleviation (302b96f324a88038a0872015466cd43783c14543):
The selector
syntax we have advised is now applied in the code, increasing its legibility greatly.
TVV-02C: Inefficient Emission of Captured Fees
Type | Severity | Location |
---|---|---|
Gas Optimization | TeaVaultV3Portfolio.sol:L297 |
Description:
The EntryFeeCollected
event is meant to indicate what entry fees were collected, however, it is redundantly emitted when the senderNotVault
variable is false
.
Example:
269uint256 totalAmount;270bool senderNotVault = msg.sender != _feeConfig.vault;271uint256 entryFeeAmount;272uint256[] memory entryFeeAmounts = new uint256[](_assets.length);273uint256 assetsLength = (totalShares == 0 ? 1 : _assets.length);274for (uint256 i; i < assetsLength; i = i + 1) {275 if (shareAmounts[i] > 0) {276 _assets[i].safeTransferFrom(msg.sender, address(this), shareAmounts[i]);277 totalAmount = totalAmount + shareAmounts[i];278 279 // collect entry fee for users280 // do not collect entry fee for fee recipient281 if (senderNotVault) {282 entryFeeAmount = shareAmounts[i].mulDiv(283 _feeConfig.entryFee,284 PERCENTAGE_MULTIPLIER,285 MathUpgradeable.Rounding.Up286 );287
288 if (entryFeeAmount > 0) {289 totalAmount = totalAmount + entryFeeAmount;290 _assets[i].safeTransferFrom(msg.sender, _feeConfig.vault, entryFeeAmount);291 entryFeeAmounts[i] = entryFeeAmount;292 }293 }294 depositedAmounts[i] = shareAmounts[i] + entryFeeAmount;295 }296}297emit EntryFeeCollected(_feeConfig.vault, entryFeeAmounts, block.timestamp);
Recommendation:
We advise the event to be solely emitted if senderNotVault
is true
, optimizing the code's gas cost.
Alleviation (302b96f324a88038a0872015466cd43783c14543):
The EntryFeeCollected
event is now solely emitted when the senderNotVault
flag is true
, optimizing the function's execution cost.
TVV-03C: Inefficient Emissions of Contextual Variable
Type | Severity | Location |
---|---|---|
Gas Optimization | TeaVaultV3Portfolio.sol:L146, L159, L179, L196, L297, L302, L324, L348, L376, L413, L639, L653, L689 |
Description:
The referenced events will emit the current block.timestamp
redundantly as it is already attached to the transaction that each event is emitted in.
Example:
689emit Swap(msg.sender, _srcToken, _dstToken, _swapRouter, _inputAmount, convertedAmount, block.timestamp);
Recommendation:
We advise such contextual information to not be emitted as part of the event, optimizing their gas cost while minimizing their data redundancy.
Alleviation (302b96f324a88038a0872015466cd43783c14543):
The Teahouse Finance team evaluated this optimization and opted not to apply it as they wish their off-chain software to be able to easily extract the timestamp
that an event was emitted in without relying on block information.
Based on this, we consider this exhibit safely acknowledged.
TVV-04C: Inefficient On-Chain Path Calculation
Type | Severity | Location |
---|---|---|
Gas Optimization | TeaVaultV3Portfolio.sol:L615-L624 |
Description:
The TeaVaultV3Portfolio::uniswapV3SwapViaSwapRouter
function is meant to be an administrative function yet calculates an entire swap path on-chain instead of accepting the path
as a direct argument of the function.
Example:
596/// @inheritdoc ITeaVaultV3Portfolio597function uniswapV3SwapViaSwapRouter(598 bool _isExactInput,599 address[] calldata _tokens,600 uint24[] calldata _fees,601 uint256 _deadline,602 uint256 _amountInOrOut,603 uint256 _amountOutOrInTolerance604) external override onlyManager nonReentrant returns (605 uint256 amountOutOrIn606) {607 address srcToken = _tokens[0];608 address dstToken = _tokens[_tokens.length - 1];609 bytes memory recommendedPath =_checkAndGetRecommendedPath(_isExactInput, srcToken, dstToken);610 address _uniswapV3SwapRouter = uniswapV3SwapRouter;611 uint256 simulatedAmount = simulateSwapViaV3Router(612 _uniswapV3SwapRouter, _isExactInput, srcToken, recommendedPath, _amountInOrOut613 );614
615 bytes memory path = abi.encodePacked(_isExactInput ? srcToken : dstToken);616 uint256 feesLength = _fees.length;617 for (uint256 i; i < feesLength; i = i + 1) {618 path = bytes.concat(619 path,620 _isExactInput ? 621 abi.encodePacked(_fees[i], _tokens[i + 1]) : 622 abi.encodePacked(_fees[feesLength - i - 1], _tokens[feesLength - i - 1])623 );624 }
Recommendation:
We advise the responsibility of constructing a valid trade path to be delegated to the manager of the portfolio, significantly reducing the on-chain gas cost of the TeaVaultV3Portfolio::uniswapV3SwapViaSwapRouter
function.
Alleviation (302b96f324a88038a0872015466cd43783c14543):
The recommendation was applied as we advised, splitting the path calculation to an external
and pure
function that the caller of the TeaVaultV3Portfolio::uniswapV3SwapViaSwapRouter
function should invoke to construct a valid _path
for it.
TVV-05C: Inefficient Ownership Transfer
Type | Severity | Location |
---|---|---|
Gas Optimization | TeaVaultV3Portfolio.sol:L114 |
Description:
The OwnableUpgradeable::transferOwnership
function invoked within TeaVaultV3Portfolio::initialize
will redundantly ensure the caller is the owner.
Example:
66function initialize(67 string calldata _name,68 string calldata _symbol,69 uint24 _feeCap,70 FeeConfig calldata _feeConfig,71 address _manager,72 ERC20Upgradeable _baseAsset,73 ERC20Upgradeable[] calldata _assets,74 AssetType[] calldata _assetTypes,75 IPool _aavePool,76 address _uniswapV3SwapRouter,77 UniswapV3PathRecommender _pathRecommender,78 IAssetOracle _assetOracle,79 IAssetOracle _aaveATokenOracle,80 IAssetOracle _teaVaultV3PairOracle,81 Swapper _swapper,82 address _owner83) public initializer {84 __UUPSUpgradeable_init();85 __Ownable_init();86 __ReentrancyGuard_init();87 __ERC20_init(_name, _symbol);88
89 SECONDS_IN_A_YEAR = 365 * 24 * 60 * 60;90 PERCENTAGE_MULTIPLIER = 1000000;91 FEE_CAP = _feeCap;92 DECIMALS = 18;93 _assignManager(_manager);94 _setFeeConfig(_feeConfig);95 aavePool = _aavePool;96 uniswapV3SwapRouter = _uniswapV3SwapRouter;97 pathRecommender = _pathRecommender;98
99 assetOracle = _assetOracle;100 aaveATokenOracle = _aaveATokenOracle;101 teaVaultV3PairOracle = _teaVaultV3PairOracle;102 swapper = _swapper;103
104 _addAsset(_baseAsset, AssetType.Base);105 for (uint256 i; i < _assets.length; i = i + 1) {106 _addAsset(_assets[i], _assetTypes[i]);107 }108 109 if (110 address(_baseAsset) != _assetOracle.getBaseAsset() ||111 address(_baseAsset) != _teaVaultV3PairOracle.getBaseAsset()112 ) revert IAssetOracle.BaseAssetMismatch();113
114 transferOwnership(_owner);115 emit TeaVaultV3PortCreated(address(this), _name, _symbol);116}
Recommendation:
We advise the internal OwnableUpgradeable::_transferOwnership
function to be invoked instead, optimizing the contract's initialization cost.
Alleviation (302b96f324a88038a0872015466cd43783c14543):
The OwnableUpgradeable::_transferOwnership
function is now correctly utilized, optimizing the code's initialization cost.
TVV-06C: Inefficient Removal of Array Element
Type | Severity | Location |
---|---|---|
Gas Optimization | TeaVaultV3Portfolio.sol:L154, L157 |
Description:
The TeaVaultV3Portfolio::removeAsset
function is inefficient in the way to removes assets as it will cache the asset-to-be-removed to a local variable (asset
), remove it, and then proceed to set its unrelated assetType
to AssetType::Null
.
Example:
149/// @inheritdoc ITeaVaultV3Portfolio150function removeAsset(uint256 _index) external override onlyOwner {151 if (assets[_index].balanceOf(address(this)) != 0) revert AssetBalanceNotZero();152 if (assetType[assets[_index]] == AssetType.Base) revert BaseAssetCannotBeRemoved();153
154 ERC20Upgradeable asset = assets[_index];155 assets[_index] = assets[assets.length - 1];156 assets.pop();157 assetType[asset] = AssetType.Null;158
159 emit AssetRemoved(address(asset), block.timestamp);160}
Recommendation:
We advise the code to set the entry's asset type to AssetType::Null
prior to removing it from the assets
array, optimizing the function's gas cost.
Alleviation (302b96f324a88038a0872015466cd43783c14543):
The Teahouse Finance team stated that the asset
is actually part of a post-removal emission and thus a cache of it is required. As such, we consider the original optimization inapplicable.
TVV-07C: Inefficient Shift Operation
Type | Severity | Location |
---|---|---|
Gas Optimization | TeaVaultV3Portfolio.sol:L155 |
Description:
The TeaVaultV3Portfolio::removeAsset
function will inefficiently overwrite the assets
element at _index
if the _index
equals the assets.length - 1
value.
Example:
149/// @inheritdoc ITeaVaultV3Portfolio150function removeAsset(uint256 _index) external override onlyOwner {151 if (assets[_index].balanceOf(address(this)) != 0) revert AssetBalanceNotZero();152 if (assetType[assets[_index]] == AssetType.Base) revert BaseAssetCannotBeRemoved();153
154 ERC20Upgradeable asset = assets[_index];155 assets[_index] = assets[assets.length - 1];156 assets.pop();157 assetType[asset] = AssetType.Null;158
159 emit AssetRemoved(address(asset), block.timestamp);160}
Recommendation:
We advise the assignment to be solely performed when _index != assets.length - 1
, optimizing the code's worst-case gas cost.
Alleviation (302b96f324a88038a0872015466cd43783c14543):
The array shift operation is now performed solely when the _index
to be replaced is different than the last element of the array (assets.length - 1
), optimizing its gas cost.
TVV-08C: Inefficient Variable Mutability (Constant)
Type | Severity | Location |
---|---|---|
Gas Optimization | TeaVaultV3Portfolio.sol:L89, L90, L92 |
Description:
The referenced variables are solely assigned to value literals in the contract's TeaVaultV3Portfolio::initialize
function.
Example:
66function initialize(67 string calldata _name,68 string calldata _symbol,69 uint24 _feeCap,70 FeeConfig calldata _feeConfig,71 address _manager,72 ERC20Upgradeable _baseAsset,73 ERC20Upgradeable[] calldata _assets,74 AssetType[] calldata _assetTypes,75 IPool _aavePool,76 address _uniswapV3SwapRouter,77 UniswapV3PathRecommender _pathRecommender,78 IAssetOracle _assetOracle,79 IAssetOracle _aaveATokenOracle,80 IAssetOracle _teaVaultV3PairOracle,81 Swapper _swapper,82 address _owner83) public initializer {84 __UUPSUpgradeable_init();85 __Ownable_init();86 __ReentrancyGuard_init();87 __ERC20_init(_name, _symbol);88
89 SECONDS_IN_A_YEAR = 365 * 24 * 60 * 60;90 PERCENTAGE_MULTIPLIER = 1000000;91 FEE_CAP = _feeCap;92 DECIMALS = 18;
Recommendation:
We advise their assignments to be relocated to their declarations and each variable's mutability to be set to constant
, significantly optimizing their read-access gas cost.
Alleviation (302b96f324a88038a0872015466cd43783c14543):
All three variables have been set as constant
per our recommendation, optimizing the code's gas cost.
TVV-09C: Inefficient Variable Mutability (Immutable)
Type | Severity | Location |
---|---|---|
Gas Optimization | TeaVaultV3Portfolio.sol:L91 |
Description:
The referenced variable is solely assigned to an input argument in the contract's TeaVaultV3Portfolio::initialize
function.
Example:
66function initialize(67 string calldata _name,68 string calldata _symbol,69 uint24 _feeCap,70 FeeConfig calldata _feeConfig,71 address _manager,72 ERC20Upgradeable _baseAsset,73 ERC20Upgradeable[] calldata _assets,74 AssetType[] calldata _assetTypes,75 IPool _aavePool,76 address _uniswapV3SwapRouter,77 UniswapV3PathRecommender _pathRecommender,78 IAssetOracle _assetOracle,79 IAssetOracle _aaveATokenOracle,80 IAssetOracle _teaVaultV3PairOracle,81 Swapper _swapper,82 address _owner83) public initializer {84 __UUPSUpgradeable_init();85 __Ownable_init();86 __ReentrancyGuard_init();87 __ERC20_init(_name, _symbol);88
89 SECONDS_IN_A_YEAR = 365 * 24 * 60 * 60;90 PERCENTAGE_MULTIPLIER = 1000000;91 FEE_CAP = _feeCap;
Recommendation:
We advise its assignment to be relocated to the contract's TeaVaultV3Portfolio::constructor
and its mutability to be set to immutable
, optimizing its read-access gas cost significantly while remaining compatible with the TeaVaultV3Portfolio
contract's upgradeable pattern.
Alleviation (302b96f324a88038a0872015466cd43783c14543):
The Teahouse Finance team stated that they do not wish to have to specify immutable
variables each time they perform a contract upgrade. As such, we consider this exhibit safely acknowledged.
TVV-10C: Loop Iterator Optimizations
Type | Severity | Location |
---|---|---|
Gas Optimization | TeaVaultV3Portfolio.sol:L105, L216, L231, L274, L342, L456, L491, L512, L523, L617 |
Description:
The linked for
loops increment / decrement their iterator "safely" due to Solidity's built - in safe arithmetics (post-0.8.X
).
Example:
105for (uint256 i; i < _assets.length; i = i + 1) {
Recommendation:
We advise the increment / decrement operations to be performed in an unchecked
code block as the last statement within each for
loop to optimize their execution cost.
Alleviation (302b96f324a88038a0872015466cd43783c14543):
The referenced loop iterator increment statements have been relocated at the end of each respective for
loop's body and have been unwrapped in an unchecked
code block, optimizing their gas cost.
TVV-11C: Redundant Local Variables
Type | Severity | Location |
---|---|---|
Gas Optimization | TeaVaultV3Portfolio.sol:L563, L705 |
Description:
The referenced local variables are assigned to a value literal (type(uint256).max
) which is inefficient as it will reserve memory when unnecessary.
Example:
705uint256 UINT256_MAX = type(uint256).max;706ERC20Upgradeable(_srcToken).approve(_uniswapV3SwapRouter, UINT256_MAX);707
708SwapRouterGenericParams memory params = SwapRouterGenericParams({709 path: _path,710 recipient: address(this),711 deadline: UINT256_MAX,712 amountInOrOut: _amountInOrOut,713 amountOutOrInTolerance: _isExactInput ? 0 : UINT256_MAX714});
Recommendation:
We advise the value literal to simply be placed in all references of each local variable, optimizing each code segment's gas cost.
Alleviation (302b96f324a88038a0872015466cd43783c14543):
The exhibit has been fully alleviated as the UINT256_MAX
variables are no longer present in the codebase.
TVV-12C: Redundant Maintenance of Total Amounts
Type | Severity | Location |
---|---|---|
Gas Optimization | TeaVaultV3Portfolio.sol:L277, L289 |
Description:
The totalAmount
variable is redundantly incremented on each deposit while being solely utilized in a non-zero check.
Example:
274for (uint256 i; i < assetsLength; i = i + 1) {275 if (shareAmounts[i] > 0) {276 _assets[i].safeTransferFrom(msg.sender, address(this), shareAmounts[i]);277 totalAmount = totalAmount + shareAmounts[i];278 279 // collect entry fee for users280 // do not collect entry fee for fee recipient281 if (senderNotVault) {282 entryFeeAmount = shareAmounts[i].mulDiv(283 _feeConfig.entryFee,284 PERCENTAGE_MULTIPLIER,285 MathUpgradeable.Rounding.Up286 );287
288 if (entryFeeAmount > 0) {289 totalAmount = totalAmount + entryFeeAmount;290 _assets[i].safeTransferFrom(msg.sender, _feeConfig.vault, entryFeeAmount);291 entryFeeAmounts[i] = entryFeeAmount;292 }293 }294 depositedAmounts[i] = shareAmounts[i] + entryFeeAmount;295 }296}297emit EntryFeeCollected(_feeConfig.vault, entryFeeAmounts, block.timestamp);298// if shares is too low such that no assets is needed, do not mint299if (totalAmount == 0) revert InvalidShareAmount();
Recommendation:
Given that it does not provide any useful data in its current state, we advise the code to instead use a bool
that is written to once indicating that at least one asset was processed.
Alleviation (302b96f324a88038a0872015466cd43783c14543):
The variable has been converted to a bool
as advised that is set to true
every time, optimizing the code's gas cost as a result.
TVV-13C: Repetitive Value Literal
Type | Severity | Location |
---|---|---|
Code Style | TeaVaultV3Portfolio.sol:L192, L405, L406, L777 |
Description:
The linked value literal is repeated across the codebase multiple times.
Example:
192if (_feeConfig.decayFactor >= 2 ** 128) revert InvalidFeeRate();
Recommendation:
We advise it to be set to a constant
variable instead optimizing the legibility of the codebase.
Alleviation (302b96f324):
While the referenced variable has been relocated to a DECAT_FACTOR_100_PERCENT
named constant, all instances referenced by the exhibit have not been properly replaced rendering it partially addressed.
Alleviation (1dc136da87):
All instances have been properly replaced by the aptly renamed DECAY_FACTOR_100_PERCENT
, addressing this exhibit in full.