Core Contracts

Regnum Aurum Acquisition Corp
HardhatReal World AssetsNFT
77,280 USDC
View results
Submission Details
Severity: low
Invalid

The same event is emitted twice in the deposit and withdraw functions of the LendingPool Contract.

01. Relevant GitHub Links

02. Summary

In the deposit function of a LendingPool Contract, the same event is fired twice, when a deposit and a withdraw are performed.

03. Vulnerability Details

In the LendingPool::deposit function, the Deposit event is being fired at the end after the deposit has been made. However, the same event is still fired in ReserveLibrary.updateReserveState.

function deposit(
uint256 amount
) external nonReentrant whenNotPaused onlyValidAmount(amount) {
// Update the reserve state before the deposit
ReserveLibrary.updateReserveState(reserve, rateData);
// Perform the deposit through ReserveLibrary
uint256 mintedAmount = ReserveLibrary.deposit(
reserve,
rateData,
amount,
msg.sender
);
// Rebalance liquidity after deposit
_rebalanceLiquidity();
@> emit Deposit(msg.sender, amount, mintedAmount);
}

The ReserveLibrary.updateReserveState function is shown below. However, the amountMinted that is passed as an argument to the actual Deposit event here is scaledamount, which may actually be an event that is used for something else. But even in this case, it's still a problem because they have the same name.

function deposit(ReserveData storage reserve,ReserveRateData storage rateData,uint256 amount,address depositor) internal returns (uint256 amountMinted) {
...
@> emit Deposit(depositor, amount, amountMinted);
return amountMinted;
}

The function withdraw has a similar problem.

04. Impact

  • This can be confusing for users and cause problems for off-chain services that rely on events to function.

05. Proof of Concept

If you run the PoC below with the command forge test --mt test_lending_pool_deposit_and_withdraw -vvvvv, you can see that the Deposit evnet and Withdraw event are generated twice, as shown below. The Withdraw evnet has slightly different arguments, but the Deposit event is exactly the same.

│ ├─ emit Deposit(user: alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6], amount: 10000000000000000000 [1e19], mintedAmount: 10000000000000000000 [1e19])
│ ├─ emit Deposit(user: alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6], amount: 10000000000000000000 [1e19], mintedAmount: 10000000000000000000 [1e19])
...
│ ├─ emit Withdraw(user: alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6], amount: 10000000000000000000 [1e19], liquidityBurned: 10000000000000000000 [1e19])
│ ├─ emit Withdraw(user: alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6], amount: 10000000000000000000 [1e19])
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {Test, console} from "forge-std/Test.sol";
import {crvUSDToken} from "src/mocks/core/tokens/crvUSDToken.sol";
import {RAACHousePrices} from "src/core/primitives/RAACHousePrices.sol";
import {RAACNFT} from "src/core/tokens/RAACNFT.sol";
import {RToken} from "src/core/tokens/RToken.sol";
import {DebtToken} from "src/core/tokens/DebtToken.sol";
import {LendingPool} from "src/core/pools/LendingPool/LendingPool.sol";
contract BaseTest is Test {
crvUSDToken public crvUSDTokenInstance;
RAACHousePrices public raacHousePricesInstance;
RAACNFT public raacNFTInstance;
RToken public rTokenInstance;
DebtToken public debtTokenInstance;
LendingPool public lendingPoolInstance;
address alice = makeAddr("alice");
address bob = makeAddr("bob");
address hyuunn = makeAddr("hyuunn");
function setUp() public {
// crvUSDToken deploy
crvUSDTokenInstance = new crvUSDToken(address(this));
// raacHousePrices deploy
raacHousePricesInstance = new RAACHousePrices(address(this));
raacHousePricesInstance.setOracle(address(this));
// raacNFT deploy
raacNFTInstance = new RAACNFT(
address(crvUSDTokenInstance),
address(raacHousePricesInstance),
address(this)
);
_mintRaacNFT();
rTokenInstance = new RToken(
"RToken",
"RTK",
address(this),
address(crvUSDTokenInstance)
);
debtTokenInstance = new DebtToken("DebtToken", "DEBT", address(this));
lendingPoolInstance = new LendingPool(
address(crvUSDTokenInstance),
address(rTokenInstance),
address(debtTokenInstance),
address(raacNFTInstance),
address(raacHousePricesInstance),
0.1e27
);
rTokenInstance.setReservePool(address(lendingPoolInstance));
debtTokenInstance.setReservePool(address(lendingPoolInstance));
}
function _mintRaacNFT() internal {
// housePrices setting
raacHousePricesInstance.setHousePrice(0, 100e18);
raacHousePricesInstance.setHousePrice(1, 50e18);
raacHousePricesInstance.setHousePrice(2, 150e18);
// crvUSDToken mint
deal(address(crvUSDTokenInstance), alice, 1000e18);
deal(address(crvUSDTokenInstance), bob, 1000e18);
deal(address(crvUSDTokenInstance), hyuunn, 1000e18);
// raacNFT mint
vm.startPrank(alice);
crvUSDTokenInstance.approve(address(raacNFTInstance), 100e18 + 1);
raacNFTInstance.mint(0, 100e18 + 1);
vm.stopPrank();
vm.startPrank(bob);
crvUSDTokenInstance.approve(address(raacNFTInstance), 50e18 + 1);
raacNFTInstance.mint(1, 50e18 + 1);
vm.stopPrank();
}
function test_lending_pool_deposit_and_withdraw() public {
vm.startPrank(alice);
crvUSDTokenInstance.approve(address(lendingPoolInstance), 10e18);
lendingPoolInstance.deposit(10e18);
assertEq(crvUSDTokenInstance.balanceOf(address(rTokenInstance)), 10e18);
rTokenInstance.balanceOf(alice);
lendingPoolInstance.withdraw(10e18);
}
}

06. Tools Used

Manual Code Review and Foundry

07. Recommended Mitigation

Emit deposit and withdraw events from only one place.

Updates

Lead Judging Commences

inallhonesty Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
inallhonesty Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.