Omniscia Tren Finance Audit

FlashLoan Manual Review Findings

FlashLoan Manual Review Findings

FLN-01M: Inexistent Support of Stablecoin Boxes

Description:

The FlashLoan::swapTokens function will fail to swap the vault's collateral to the debt token if the vault's collateral is the stableCoin itself which is incorrect.

Impact:

Any Tren box backed by the stableCoin will not be able to be repaid via a flash-loan due to the Uniswap V3 exact output swap failing when attempting to find a suitable pool to execute the swap step in.

Example:

contracts/FlashLoan.sol
189/**
190 * @dev Swaps as little as possible of one token for `amountOut` of another along the specified
191 * path (reversed).
192 * @param _tokenIn The address of input collateral.
193 * @param _collAmountIn The amount of collateral which will be swapped.
194 * @param _debtAmountOut The amount of trenUSD which will (should) be received.
195 */
196function swapTokens(address _tokenIn, uint256 _collAmountIn, uint256 _debtAmountOut) private {
197 // Approve swapRouter to spend amountInMaximum
198 IERC20(_tokenIn).approve(address(swapRouter), _collAmountIn);
199
200 // The tokenIn/tokenOut field is the shared token between the two pools used in the multiple
201 // pool swap. In this case stable coin is the "shared" token.
202 // For an exactOutput swap, the first swap that occurs is the swap which returns the
203 // eventual desired token.
204 // In this case, our desired output token is debtToken so that swap happpens first, and is
205 // encoded in the path accordingly.
206 IUniswapRouterV3.ExactOutputParams memory params = IUniswapRouterV3.ExactOutputParams({
207 path: abi.encodePacked(address(debtToken), uint24(3000), stableCoin, uint24(3000), _tokenIn),
208 recipient: address(this),
209 deadline: block.timestamp,
210 amountOut: _debtAmountOut,
211 amountInMaximum: _collAmountIn
212 });
213
214 // Executes the swap, returning the amountIn actually spent.
215 uint256 amountIn = swapRouter.exactOutput(params);
216
217 // If the swap did not require the full _collAmountIn to achieve the exact amountOut then we
218 // refund msg.sender and approve the router to spend 0.
219 if (amountIn < _collAmountIn) {
220 IERC20(_tokenIn).approve(address(swapRouter), 0);
221 IERC20(_tokenIn).transfer(msg.sender, _collAmountIn - amountIn);
222 }
223}

Recommendation:

We advise the code to encode a different path if the _tokenIn matches the stableCoin and potentially use the more efficient IUniswapRouterV3::exactOutputSingle function, ensuring that Tren boxes backed by the stableCoin can be repaid via a flash-loan.

Alleviation (f6f1ad0b8f):

The code was updated to differentiate the stableCoin by evaluating the collateral token's decimals which is an approach we do not advise as many tokens may have 6 decimals.

We advise a proper comparison to be made of the _collTokenIn address itself, potentially utilizing a mapping if multiple tokens are expected to be directly swapped to the debt token.

Alleviation (73b9546eb9):

A proper mapping has been introduced that can be mutated by the owner of the contract and indicates whether a direct swap should be performed, alleviating this exhibit.

FLN-02M: Invalid Call Context

Description:

The FlashLoan::flashLoanForRepay function will fail to behave as expected due to improperly performing a repayment for the asset of the caller. Specifically, the invocation of BorrowerOperations::repayDebtTokens function will result in the caller being the FlashLoan contract itself instead of the caller of the FlashLoan::flashLoanForRepay function, resulting in the Tren box of the FlashLoan contract to be repaid instead of the Tren box of the caller.

Impact:

The FlashLoan::flashLoanForRepay function flow will not behave as expected in its current state, rendering it invalid.

Example:

contracts/FlashLoan.sol
104/**
105 * @notice Executes a flash loan specifically to repay a debt on the provided asset.
106 * @param _asset The address of the asset for which the debt is to be repaid.
107 * @dev This function initiates a flash loan transaction to repay a debt on the specified asset.
108 */
109function flashLoanForRepay(address _asset) external nonReentrant {
110 if (!IAdminContract(adminContract).getIsActive(_asset)) {
111 revert FlashLoan__CollateralIsNotActive();
112 }
113 uint256 debt = ITrenBoxManager(trenBoxManager).getTrenBoxDebt(_asset, msg.sender);
114 uint256 gasCompensation = IAdminContract(adminContract).getDebtTokenGasCompensation(_asset);
115 uint256 refund = IFeeCollector(feeCollector).simulateRefund(msg.sender, _asset, 1 ether);
116 uint256 netDebt = debt - gasCompensation - refund;
117
118 mintTokens(netDebt);
119
120 IDebtToken(debtToken).transfer(msg.sender, netDebt);
121
122 uint256 fee = calculateFee(netDebt);
123
124 IBorrowerOperations(borrowerOperations).repayDebtTokens(
125 _asset, netDebt, address(0), address(0)
126 );
127
128 uint256 collAmountIn = IERC20(_asset).balanceOf(address(this));
129 uint256 debtTokensToGet = netDebt + fee;
130
131 swapTokens(_asset, collAmountIn, debtTokensToGet);
132
133 if (IDebtToken(debtToken).balanceOf(address(this)) < debtTokensToGet) {
134 revert FlashLoan__LoanIsNotRepayable();
135 }
136
137 burnTokens(netDebt);
138
139 sendFeeToCollector();
140
141 emit FlashLoanExecuted(msg.sender, netDebt, fee);
142}

Recommendation:

We advise the flash-loan based repayment flow to be revised, either by introducing a special function in the BorrowerOperations contract or by once again performing a callback to the caller.

Alleviation (f6f1ad0b8f24a96ade345db1dd05a1878eb0f761):

A new BorrowerOperations::repayDebtTokensWithFlashloan function was introduced that permits the Tren box of another user to be repaid, and logic within the Tren box closure function was introduced to transmit tokens to the flash-loan contract address instead of the borrower whenever the flash-loan contract performs the Tren box closure.

As such, we consider this exhibit fully alleviated.