BriVault

First Flight #52
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Severity: high
Valid

Users cannot withdraw assets even if they are winners

briVault.sol::deposit() incorrectly credits vault shares to msg.sender regardless of the intended recipient

Description

  • Normal behavior - The deposit functionality should mint shares to the receiver that can be msg.sender or a different address.

  • Issue - If one user wants to deposit for a different user, then the logical error comes in place minting shares to the wrong address. However, stakedAsset[receiver] is being updated and this allows that user to join an event. But his shares are 0. When the winner country is set and if the case is that the user has won, he will actually receive nothing since the withdraw will either revert or transfer 0 amount to the user.


function deposit(uint256 assets, address receiver) public override returns (uint256) {
require(receiver != address(0));
if (block.timestamp >= eventStartDate) {
revert eventStarted();
}
uint256 fee = _getParticipationFee(assets);
console.log("fee ", fee);
// charge on a percentage basis points
if (minimumAmount + fee > assets) {
revert lowFeeAndAmount();
}
uint256 stakeAsset = assets - fee;
stakedAsset[receiver] = stakeAsset;
uint256 participantShares = _convertToShares(stakeAsset);
IERC20(asset()).safeTransferFrom(msg.sender, participationFeeAddress, fee);
IERC20(asset()).safeTransferFrom(msg.sender, address(this), stakeAsset);
//@audit wrong address
@> _mint(msg.sender, participantShares);
emit deposited (receiver, stakeAsset);
return participantShares;
}

Risk

Likelihood:

  • This is going to occur everytime a user wants to deposit assets for someone else


Impact:

  • If the receiver is the winner, he will receive nothing.

Proof of Concept

  1. User deposits asset into the vault for user2 as receiver

  2. User2 is now eligible to join the event as the deposited assets are correctly credited to their account, however the shares are NOT.

  3. Time passes and the event has ended , owner sets the winner country.

  4. User2 is the winner but receives 0 assets back or transaction reverts, we have 2 cases:

    case1: if user2 is the only winner , withdraw tx will revert due to division with 0.

    case2: If there are multiple winners , assetToWithdraw is 0 due to the fact that User2 shares are 0 and the math for calculating the assets being transfered will always be 0 , since shares(0) multiplied with a number will always be 0. Consider the POC below.


    function test_wrongReceiver() public {
    vm.startPrank(owner);
    briVault.setCountry(countries);
    vm.stopPrank();
    vm.startPrank(user1);
    uint256 balanceUser1Vault = briVault.balanceOf(user1);
    console.log("user1 balance first time ", balanceUser1Vault);
    assertEq(0, balanceUser1Vault);
    mockToken.approve(address(briVault), 15 ether);
    briVault.deposit(15 ether, user2);
    assertGt(briVault.balanceOf(user1), balanceUser1Vault);
    assertEq(0, briVault.balanceOf(user2));
    vm.stopPrank();
    vm.startPrank(user2);
    briVault.joinEvent(0);
    vm.stopPrank();
    vm.startPrank(user3);
    mockToken.approve(address(briVault), 5 ether);
    briVault.deposit(5 ether, user3);
    briVault.joinEvent(10);
    vm.stopPrank();
    vm.warp(eventEndDate + 1);
    vm.startPrank(owner);
    briVault.setWinner(0);
    console.log(briVault.finalizedVaultAsset());
    vm.stopPrank();
    vm.prank(user3);
    vm.expectRevert();
    briVault.withdraw();
    vm.prank(user2);
    vm.expectRevert();
    briVault.withdraw();
    } // case1
    function test_wrongReceiver() public {
    vm.startPrank(owner);
    briVault.setCountry(countries);
    vm.stopPrank();
    vm.startPrank(user1);
    uint256 balanceUser1Vault = briVault.balanceOf(user1);
    console.log("user1 balance first time ", balanceUser1Vault);
    assertEq(0, balanceUser1Vault);
    mockToken.approve(address(briVault), 15 ether);
    briVault.deposit(15 ether, user2);
    assertGt(briVault.balanceOf(user1), balanceUser1Vault);
    assertEq(0, briVault.balanceOf(user2));
    vm.stopPrank();
    vm.startPrank(user2);
    // user2 winner
    briVault.joinEvent(0);
    vm.stopPrank();
    vm.startPrank(user3);
    mockToken.approve(address(briVault), 5 ether);
    briVault.deposit(5 ether, user3);
    // user3 winner
    briVault.joinEvent(0);
    vm.stopPrank();
    vm.warp(eventEndDate + 1);
    vm.startPrank(owner);
    briVault.setWinner(0);
    console.log(briVault.finalizedVaultAsset());
    vm.stopPrank();
    vm.prank(user2);
    briVault.withdraw();
    } // case2, we can see the emited events
    BriVault::withdraw()
    │ ├─ [0] console::log("shares ", 0) [staticcall]
    │ │ └─ ← [Stop]
    │ ├─ emit Transfer(from: user2: [0x537C8f3d3E18dF5517a58B3fB9D9143697996802], to: 0x0000000000000000000000000000000000000000, value: 0)
    │ ├─ [5281] MockERC20::transfer(user2: [0x537C8f3d3E18dF5517a58B3fB9D9143697996802], 0)
    │ │ ├─ emit Transfer(from: BriVault: [0x88F59F8826af5e695B13cA934d6c7999875A9EeA], to: user2: [0x537C8f3d3E18dF5517a58B3fB9D9143697996802], value: 0)
    │ │ └─ ← [Return] true
    │ ├─ emit Withdraw(user: user2: [0x537C8f3d3E18dF5517a58B3fB9D9143697996802], _amount: 0)
    │ └─ ← [Stop]
    └─ ← [Stop]

Recommended Mitigation

Mint shares to the receiver instead to msg.sender. This ensures the correct logic of the deposit function

function deposit(uint256 assets, address receiver) public override returns (uint256) {
require(receiver != address(0));
if (block.timestamp >= eventStartDate) {
revert eventStarted();
}
uint256 fee = _getParticipationFee(assets);
console.log("fee ", fee);
// charge on a percentage basis points
if (minimumAmount + fee > assets) {
revert lowFeeAndAmount();
}
uint256 stakeAsset = assets - fee;
stakedAsset[receiver] = stakeAsset;
uint256 participantShares = _convertToShares(stakeAsset);
IERC20(asset()).safeTransferFrom(msg.sender, participationFeeAddress, fee);
IERC20(asset()).safeTransferFrom(msg.sender, address(this), stakeAsset);
- _mint(msg.sender, participantShares);
+ _mint(receiver,participantShares);
emit deposited (receiver, stakeAsset);
return participantShares;
}
Updates

Appeal created

bube Lead Judge 19 days ago
Submission Judgement Published
Validated
Assigned finding tags:

Shares Minted to msg.sender Instead of Specified Receiver

Support

FAQs

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

Give us feedback!