There is lack of validation for cashAmountIn in BuyCreditMarket when exactAmountIn == false. Since the fee is charged on the amount of swapped cash, the fee will be 1 AToken if the amount of swapped cash is 1 AToken. An attacker can exploit this to create an arbitrary loan with only 1 AToken fee by setting a high APR sell credit limit order, and then buying a desired amount of credit from himself via buy credit market order with exactAmountIn == false. This behavior could be escalated to two large impacts.
First impact
An attacker can DoS on market orders that using his credit.
Bob (the attacker) sells credit to Alice, Alice has a credit position (id: A, amount: K)
Bob creates an exact credit position to the credit position that he sold to Alice (same credit amount and same due date). Now, Bob has a credit position (id: B, amount: K)
Alice uses sell credit market order to sell the credit position of id A to Candy
Bob front-run Alice's sell credit market order with compensating his debt with the credit position of id B
Since the credit position of id A has been repaid, Alice's sell credit market order will revert
Using the above exploit, Bob pays only 1 AToken fee in step 2. By compensating in step 4, Bob's debt to Alice was reduced. After the attack, Bob only owes Alice K credit from step 2.
Same idea can be applied to DoS buy credit market order.
Second impact
Users can trade credit with each other with only 1 AToken fee
First they need to setup a contract Market that have three functions:
buyCreditLimit
Market takes X amount of USDC to be lent from the lender
Market takes ETH from the lender. Market deposits ETH for collateral
Market set high APR sell credit limit order
The lender buy X credit from Market. The lender pays only 1 AToken fee
Market reset sells credit limit order
sellCreditMarket
The borrower set high APR sell credit limit
Market buy X credit from the borrower. Market pays only 1 AToken
Market compensates its debt to the lender using the credit from the borrower
Market sends X USDC to the borrower
The borrower reset sell credit limit.
cancelBuyCreditLimit
Market deposit X USDC to receive AToken
Market repays its debt to the lender
Market withdraws WETH and sends back to the lender
Although the functionality of Market is limited when comparing to the protocol's market, because this market only supports the lender as a maker and the borrower as a taker, but the term of the loan could be negotiated by private messages.
Note that, the whole process is trustless. The lender can always cancel the buy credit limit to claim back USDC and WETH. The borrower is not exposed to any risk.
Proof of Concept
// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
import {BaseTest} from "@test/BaseTest.sol";
import {YieldCurveHelper} from "@test/helpers/libraries/YieldCurveHelper.sol";
import {RESERVED_ID} from "@src/libraries/LoanLibrary.sol";
import {Vars} from "@test/BaseTest.sol";
import {console} from "forge-std/Test.sol";
contract POC is BaseTest {
function test() public {
_deposit(alice, weth, 200e18);
_deposit(alice, usdc, 100e6);
uint256 tenor = 365 days;
uint256 amount = 20e6;
Vars memory _before = _state();
_sellCreditLimit(alice, YieldCurveHelper.pointCurve(tenor, 1e60));
_buyCreditMarket(alice, alice, RESERVED_ID, amount, tenor, false);
Vars memory _after = _state();
console.log("AToken before: %d", _before.alice.borrowATokenBalance);
console.log("AToken after: %d", _after.alice.borrowATokenBalance);
console.log("Credit after: %d", _after.alice.debtBalance);
}
}
Lines of code
https://github.com/code-423n4/2024-06-size/blob/8850e25fb088898e9cf86f9be1c401ad155bea86/src/libraries/actions/BuyCreditMarket.sol#L170-L176
Vulnerability details
Impact
There is lack of validation for
cashAmountIn
inBuyCreditMarket
whenexactAmountIn == false
. Since the fee is charged on the amount of swapped cash, the fee will be1 AToken
if the amount of swapped cash is1 AToken
. An attacker can exploit this to create an arbitrary loan with only1 AToken
fee by setting a high APR sell credit limit order, and then buying a desired amount of credit from himself via buy credit market order withexactAmountIn == false
. This behavior could be escalated to two large impacts.First impact
An attacker can DoS on market orders that using his credit.
(id: A, amount: K)
(id: B, amount: K)
A
to CandyB
A
has been repaid, Alice's sell credit market order will revertUsing the above exploit, Bob pays only
1 AToken
fee in step 2. By compensating in step 4, Bob's debt to Alice was reduced. After the attack, Bob only owes AliceK
credit from step 2.Same idea can be applied to DoS buy credit market order.
Second impact
Users can trade credit with each other with only
1 AToken
feeFirst they need to setup a contract
Market
that have three functions:buyCreditLimit
Market
takesX
amount of USDC to be lent from the lenderMarket
takes ETH from the lender.Market
deposits ETH for collateralMarket
set high APR sell credit limit orderX
credit fromMarket
. The lender pays only1 AToken
feeMarket
reset sells credit limit ordersellCreditMarket
Market
buyX
credit from the borrower.Market
pays only1 AToken
Market
compensates its debt to the lender using the credit from the borrowerMarket
sendsX
USDC to the borrowercancelBuyCreditLimit
Market
depositX
USDC to receive ATokenMarket
repays its debt to the lenderMarket
withdraws WETH and sends back to the lenderAlthough the functionality of
Market
is limited when comparing to the protocol's market, because this market only supports the lender as a maker and the borrower as a taker, but the term of the loan could be negotiated by private messages.Note that, the whole process is trustless. The lender can always cancel the buy credit limit to claim back USDC and WETH. The borrower is not exposed to any risk.
Proof of Concept
Logs:
Tools Used
Manual Review.
Recommended Mitigation Steps
Validate
cashAmountIn
inBuyCreditMarket
whenexactAmountIn == false
.Assessed type
Invalid Validation