LOW severity: `ROUSG::unwrap` can unnecessarily return slightly less `OUSG` tokens than users originally wrapped. **Description:** One invariant of the `ROUSG` token is:

> when unwrapping users should receive the same amount of OUSG input tokens they provided when they wrapped, irrespective of price

However this can often not be the case as `ROUSG::unwrap` can unnecessarily return slightly less `OUSG`...

Vulnerable code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import "forge-std/Test.sol";

// run from base project directory with:
// forge test --match-contract ROUSGWrapUnwrapBrokenInvariantTest -vvv

contract ROUSGWrapUnwrapBrokenInvariantTest is Test {

    uint256 public constant OUSG_TO_ROUSG_SHARES_MULTIPLIER = 10_000;

    function _getROUSGByShares(uint256 _shares, uint256 ousgPrice) internal pure returns (uint256 rOUSGAmount) {
        rOUSGAmount = (_shares * ousgPrice) / (1e18 * OUSG_TO_ROUSG_SHARES_MULTIPLIER);
    }

    function getSharesByROUSG(uint256 _rOUSGAmount, uint256 ousgPrice)
    internal pure returns (uint256 shares) {
        shares = (_rOUSGAmount * 1e18 * OUSG_TO_ROUSG_SHARES_MULTIPLIER) / ousgPrice;
    }

    function _wrap(uint256 _OUSGAmount) internal pure returns (uint256 shares) {
        require(_OUSGAmount > 0, "rOUSG: can't wrap zero OUSG tokens");

        shares = _OUSGAmount * OUSG_TO_ROUSG_SHARES_MULTIPLIER;
    }

    function _unwrap(uint256 _rOUSGAmount, uint256 ousgPrice) internal pure returns(uint256 tokens) {
        require(_rOUSGAmount > 0, "rOUSG: can't unwrap zero rOUSG tokens");

        uint256 ousgSharesAmount = getSharesByROUSG(_rOUSGAmount, ousgPrice);

        vm.assume(ousgSharesAmount >= OUSG_TO_ROUSG_SHARES_MULTIPLIER);

        tokens = ousgSharesAmount / OUSG_TO_ROUSG_SHARES_MULTIPLIER;
    }

    function test_WrapUnwrapReturnsInputTokens(uint256 initialOUSGAmount, uint256 ousgPrice) external {
        // bound inputs
        initialOUSGAmount  = bound(initialOUSGAmount, 100000e18, type(uint128).max);
        ousgPrice          = bound(ousgPrice, 105e18, 106e18);

        // wrap OUSG into rOUSG
        uint256 rousgShares = _wrap(initialOUSGAmount);

        // get the token amount of rOUSG equivalent to the received shares
        uint256 rousgAmount = _getROUSGByShares(rousgShares, ousgPrice);

        // use the token amount to unwrap rOUSG back into OUSG
        uint256 finalOUSGAmount = _unwrap(rousgAmount, ousgPrice);

        // verify amounts match; this fails as user is slighty short-changed
        assertEq(initialOUSGAmount, finalOUSGAmount);
    }
}

// @audit this function allow unwrapping by shares instead of tokens
  // to prevent users being slightly short-changed such that users will
  // always receive the same input amount of OUSG tokens
  function unwrapShares(uint256 _shares) external whenNotPaused {
    uint256 ousgTokens = _shares / OUSG_TO_ROUSG_SHARES_MULTIPLIER;

    require(ousgTokens > 0, "rOUSG: no tokens to send, unwrap more shares");

    uint256 rousgBurned = getROUSGByShares(_shares);

    _burnShares(msg.sender, _shares);
    ousg.transfer(msg.sender, ousgTokens);

    emit Transfer(msg.sender, address(0), rousgBurned);
    emit TransferShares(msg.sender, address(0), _shares);
  }

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import "forge-std/Test.sol";

// run from base project directory with:
// forge test --match-contract ROUSGWrapUnwrapFixedInvariantTest -vvv

contract ROUSGWrapUnwrapFixedInvariantTest is Test {

    uint256 public constant OUSG_TO_ROUSG_SHARES_MULTIPLIER = 10_000;

    function _wrap(uint256 _OUSGAmount) internal pure returns (uint256 shares) {
        require(_OUSGAmount > 0, "rOUSG: can't wrap zero OUSG tokens");

        shares = _OUSGAmount * OUSG_TO_ROUSG_SHARES_MULTIPLIER;
    }

    function _unwrapShares(uint256 shares) internal pure returns(uint256 tokens) {
        tokens = shares / OUSG_TO_ROUSG_SHARES_MULTIPLIER;
    }

    function test_WrapUnwrapReturnsInputTokens(uint256 initialOUSGAmount, uint256 ousgPrice) external {
        // bound inputs
        initialOUSGAmount  = bound(initialOUSGAmount, 100000e18, type(uint128).max);
        ousgPrice          = bound(ousgPrice, 105e18, 106e18);

        // wrap OUSG into rOUSG
        uint256 rousgShares = _wrap(initialOUSGAmount);

        // use the token amount to unwrap rOUSG back into OUSG
        uint256 finalOUSGAmount = _unwrapShares(rousgShares);

        assertEq(initialOUSGAmount, finalOUSGAmount);
    }
}