xanoutas/solidity-security-findings huggingface dataset:
Dust amount stuck in rUSDY.sol when wrapping and unwrapping part of the full amounts
Audit: 2023-09-ondo-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-ondo/blob/47d34d6d4a5303af5f46e907ac2292e6a7745f6c/contracts/usdy/rUSDY.sol#L434-L456


# Vulnerability details

## Impact

Dust amount of shares stuck in the contract. 

## Proof of Concept

When wrapping and unwrapping USDY to rUSDY, there is a moment where some shares get stuck in the contract. Normally, if a user wraps 100 USDY for 10 days and unwraps the rUSDY.balanceOf(user), he will get his full amount of USDY back. However, if the user wraps 100 USDY for 10 days and decides to unwrap 99 rUSDY, wait for another 10 days and unwrap the rest of the rUSDY balance, some shares will get stuck in the contract and the user will not be able to get the dust amount of rUSDY (~9400 wei of shares) back to USDY. 

I have written a test to show this problem. Insert the test into the rUSDY_dynamic.t.sol file and run the command: 

> forge test --match-test test_stuck_rUSDY_wrap_wait_unwrap -vv

```
 function test_stuck_rUSDY_wrap_wait_unwrap() public dealAlice {
    deal(address(usdy),address(alice),300e18);
        console.log("The new rusdy balance of alice: ", rUSDYToken.balanceOf(alice));
    console.log("The usdy balance of alice: ", usdy.balanceOf(alice));
    console.log("The new shares of alice: ", rUSDYToken.sharesOf(alice));
    console.log("-----------------AFTER WRAP----------------------");
    vm.startPrank(alice);
    usdy.approve(address(rUSDYToken), 300e18);
    
    rUSDYToken.wrap(100e18);
    console.log("The new rusdy balance of alice: ", rUSDYToken.balanceOf(alice));
    console.log("The usdy balance of alice: ", usdy.balanceOf(alice));
    console.log("The new shares of alice: ", rUSDYToken.sharesOf(alice));
    
    console.log("-----------------AFTER 10 DAYS----------------------");

    vm.warp(block.timestamp + 10 days);

    console.log("The new rusdy balance of alice: ", rUSDYToken.balanceOf(alice));
    console.log("The usdy balance of alice: ", usdy.balanceOf(address(rUSDYToken)));
    console.log("The new shares of alice: ", rUSDYToken.sharesOf(alice));
    console.log("-----------------AFTER UNWRAP----------------------");

    rUSDYToken.unwrap(99e18);
    console.log("The new rusdy balance of alice: ", rUSDYToken.balanceOf(alice));
    console.log("The usdy balance of alice: ", usdy.balanceOf(alice));
    console.log("The new shares of alice: ", rUSDYToken.sharesOf(alice));
   console.log("-----------------AFTER 10 DAYS----------------------");
    vm.warp(block.timestamp + 10 days);

    rUSDYToken.unwrap(rUSDYToken.balanceOf(alice));
        console.log("The new rusdy balance of alice: ", rUSDYToken.balanceOf(alice));
    console.log("The usdy balance of alice: ", usdy.balanceOf(alice));
    console.log("The new shares of alice: ", rUSDYToken.sharesOf(alice));
    console.log("--------------------------------------------------------");
```

The test should pass with these logs:

```
[PASS] test_stuck_rUSDY_wrap_wait_unwrap() (gas: 570059)
Logs:
  The new rusdy bala
The user is not able to specify the deadline for voting on his transaction on the destination chain.
Audit: 2023-09-ondo-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-ondo/blob/47d34d6d4a5303af5f46e907ac2292e6a7745f6c/contracts/bridge/DestinationBridge.sol#L110


# Vulnerability details

## Impact
The user may lose his tokens if the voting on his transaction on the destination chain is ignored, or if the voting takes too long and the transaction is approved in the end, then the transfer tokens may become irrelevant for the user because too much time has passed. The user must be able to specify a deadline for bridging tokens - for voting.

## Proof of Concept
1. User call burnAndCallAxelar() on source chain and his tokens burns. Bridge contract call Axelar contract.
2. Axelar call DestinationBridge contract and _execute function call internal function _attachThreshold, which add threshhold for votes in the poll. 
3. A lot of time passes - approvers (users with special role) dont voting for this tx...
4. User could not recover his burned tokens on source chain.

## Tools Used
Manual review

## Recommended Mitigation Steps
Allow user specify deadline for bridge operation on source chain.


## Assessed type

Other
Some transactions may get stuck due to low fees
Audit: 2023-09-ondo-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-ondo/blob/main/contracts/bridge/SourceBridge.sol#L96


# Vulnerability details

## Impact
Transactions sent from the `SourceBridge` contract with a low `msg.value `will get stuck. When users pay for the gas of a transaction, they call the `payNativeGasForContractCall` function of the `GAS_RECEIVER` contract. This acts as a tax for sending the transaction. Occasionally, the prepaid gas in the Gas Receiver contract could be insufficient, especially when the destination chain is congested with many transfers or due to other conditions.

Unfortunately, if the `msg.value` is too small, the transaction will be stuck and never executed from the Axeler Gateway to the `DestinationBridge` contract.

## Proof of Concept
This is mentioned in the Axelar documentation: [link](https://docs.axelar.dev/dev/general-message-passing/gas-services/increase-gas)

## Tools Used
Manual Review

## Recommended Mitigation Steps
Axelar provides an option to resubmit a new amount of gas, as well as an option to refund the paid gas. The `SourceBridge` contract should implement the calling of the `addNativeGas` function of the Gas Receiver contract.

For more information: [docs](https://docs.axelar.dev/dev/general-message-passing/gas-services/increase-gas)


## Assessed type

DoS
Funds designated for a refund may become unrecoverable
Audit: 2023-09-ondo-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-ondo/blob/47d34d6d4a5303af5f46e907ac2292e6a7745f6c/contracts/bridge/SourceBridge.sol#L96-L102


# Vulnerability details

## Proof-of-Concept

The prepaid gas allocated for `payGasForContractCall` might surpass the actual amount required for relaying a message to the destination contract.
The Executor Service autonomously monitors the surplus gas and initiates a refund to the payer's wallet address by invoking the Refund function in the Gas Service contract.
When a user calls the [SourceBridge.burnAndCallAxelar]((https://github.com/code-423n4/2023-09-ondo/blob/47d34d6d4a5303af5f46e907ac2292e6a7745f6c/contracts/bridge/SourceBridge.sol#L61)) function and provides a msg.value greater than the actual amount necessary for relaying a message, they have the opportunity to receive a refund for the excess funds.

## Impact
If the msg.sender is a smart contract, there is a possibility that it may not have a function to receive or withdraw native tokens.

## Tools Used
Manual

## Recommended Mitigation Steps
Implement the ability to incorporate a refundAddress parameter within the burnAndCallAxelar function, rather than depending exclusively on msg.sender for the refund.

```solidity
function burnAndCallAxelar(
    uint256 amount,
    string calldata destinationChain,
	  address refundAddress
  ) external payable whenNotPaused {
  
  
  
  function _payGasAndCallContract(
    string calldata destinationChain,
    string memory destContract,
	  address refundAddress,
    bytes memory payload
  ) private {
    GAS_RECEIVER.payNativeGasForContractCall{value: msg.value}(
      address(this),
      destinationChain,
      destContract,
      payload,
      refundAddress
    );

    // Send all information to AxelarGateway contract.
    AXELAR_GATEWAY.callContract(destinationChain, destContract, payload);
  }
```
Further information about this function can be found [here](https://docs.axelar.dev/dev/general-message-passing/gas-services/refund)


## Assessed type

ETH-Transfer
All bridged funds will be lost for the users using the account abstraction wallet
Audit: 2023-09-ondo-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-ondo/blob/47d34d6d4a5303af5f46e907ac2292e6a7745f6c/contracts/bridge/SourceBridge.sol#L61-L82
https://github.com/code-423n4/2023-09-ondo/blob/47d34d6d4a5303af5f46e907ac2292e6a7745f6c/contracts/bridge/DestinationBridge.sol#L85-L114


# Vulnerability details

## Impact
Users with account abstraction wallets have different address across different chains for same account, so if someone using account abstraction wallet bridge the asset, assets will be minted to wrong address and lost permanently
## Proof of Concept
Account abstraction wallet have been on the rise for quite a time now and have alot of users, just look at the figures by safe wallet (one of the account abstraction wallet) 
![image](https://user-images.githubusercontent.com/68193826/266396616-85f91746-9968-4424-81f6-35d053fef047.png)

4.4 million users and 5.4 billion assets, there is very high risk that safe wallet users will try to bridge the assets and lost them.

Now look at the codebase and understand how the assets will be lost.

In source bridge in `burnAndCallAxelar` we construct the payload as follow :


here we can see the payload passes msg.sender as receiving address on other chain assuming that user have same address across all the evm chains, which is not the case if user is using the account abstraction wallet


```solidity
    bytes memory payload = abi.encode(VERSION, msg.sender, amount, nonce++);
```

and than call the following function passing the payload, which calls the callContract function passing the payload to axelar network

```solidity
  function _payGasAndCallCotract(
    string calldata destinationChain,
    string memory destContract,
    bytes memory payload
  ) private {
    GAS_RECEIVER.payNativeGasForContractCall{value: msg.value}(
      address(this),
      destinationChain,
      destContract,
      payload,
      msg.sender
    );

    // Send all information to AxelarGateway contract.
    AXELAR_GATEWAY.callContract(destinationChain, destContract, payload);
  }
  
```

Than on the destination any axelar node will call the `excute()` function passing in the payload and the tokens will be minted to account abstraction wallet address of the source chain but on destination same person will not be the owner of that address, and hence tokens are permanently lost

```solidity
  function _execute(
    string calldata srcChain,
    string calldata srcAddr,
    bytes calldata payload
  ) internal override whenNotPaused {
  
    (bytes32 version, address srcSender, uint256 amt, uint256 nonce) = abi
      .decode(payload, (bytes32, address, uint256, uint256));

    if (version != VERSION) {
      revert InvalidVersion();
    }
    if (chainToApprovedSender[srcChain] == bytes32(0)) {
      revert ChainNotSupported();
    }
    // each chain have only on approved sender that is the source bridge contract.
    if (chainToApprovedSender[srcChain] != keccak256(abi.encode(srcAddr))) {
      revert SourceNotSupport
Gas Price Vulnerability in _payGasAndCallContract Function
Audit: 2023-09-ondo-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-ondo/blob/main/contracts/bridge/SourceBridge.sol#L96


# Vulnerability details

## Impact
The _payGasAndCallContract function within the contract is susceptible to a gas price vulnerability that can disrupt the intended functionality and create a dilemma for operators. If a user specifies an unreasonably low gas price when initiating a transaction, it can lead to the following impacts:

Lockout Risk: The low gas price may result in miners or validators not including the transaction in a block, effectively locking the operator out of the operation.

Financial Loss: To ensure the transaction's completion, the operator may have to execute the transaction at their own expense, potentially leading to financial loss or an unsustainable situation.


## Proof of Concept


    function _payGasAndCallContract(
    string calldata destinationChain,
    string memory destContract,
    bytes memory payload
    ) private {
    GAS_RECEIVER.payNativeGasForContractCall{value: msg.value}(
      address(this),
      destinationChain,
      destContract,
      payload,
      msg.sender
    );

    // Send all information to AxelarGateway contract.
    AXELAR_GATEWAY.callContract(destinationChain, destContract, payload);
    }

Risk Scenario:

User Transaction: A user initiates a transaction to the contract, specifying a very low gas price.

Gas Payment: The contract uses the provided msg.value to pay for the gas required for the contract call and the cross-chain operation. However, the low gas price may not incentivize miners or validators to include the transaction in a block.

Operator Dilemma: The operator faces a dilemma of either waiting for the low-priced transaction to be mined, potentially causing a lockout situation, or executing the transaction at their own expense to ensure its completion.


## Tools Used
Manual review

## Recommended Mitigation Steps
Implement gas price validation to ensure that the user's specified gas price is within a reasonable range. Reject transactions with excessively low gas prices.


## Assessed type

Math
Lack of Refund Mechanism for Overpayment of msg.value in _payGasAndCallContract Function
Audit: 2023-09-ondo-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-ondo/blob/main/contracts/bridge/SourceBridge.sol#L96


# Vulnerability details

## Impact
Detailed description of the impact of this finding.

## Proof of Concept

    function _payGasAndCallContract(
    string calldata destinationChain,
    string memory destContract,
    bytes memory payload
    ) private {
    GAS_RECEIVER.payNativeGasForContractCall{value: msg.value}(
      address(this),
      destinationChain,
      destContract,
      payload,
      msg.sender
    );

    // Send all information to AxelarGateway contract.
    AXELAR_GATEWAY.callContract(destinationChain, destContract, payload);
    }

Risk Scenario:

User Transaction: A user initiates a transaction to the contract, calling the _payGasAndCallContract function and provides an amount of ether (msg.value) that exceeds the actual gas cost required for the operation.

Gas Payment: The contract uses the provided msg.value to pay for the gas required for the contract call and the cross-chain operation.

Excess Ether: If the actual gas cost is lower than the provided msg.value, the excess ether is not refunded to the user but remains within the contract.

Accumulation: Over time, if multiple users overpay for gas, the contract can accumulate excess ether that becomes locked and unrecoverable, potentially leading to a financial loss for users.

## Tools Used
MAnual review

## Recommended Mitigation Steps
Refund Excess Ether: Modify the _payGasAndCallContract function to accurately calculate the actual gas cost of the operation and refund any excess ether to the sender.

Gas Cost Calculation: Implement precise gas cost calculation to determine the correct amount to be refunded.


## Assessed type

ETH-Transfer
`RWADynamicOracle.simulateRange` COULD PROVIDE WRONG DERIVED PRICES IF INVALID `endTime` VALUE IS PASSED IN
Audit: 2023-09-ondo-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-ondo/blob/main/contracts/rwaOracles/RWADynamicOracle.sol#L104-L110
https://github.com/code-423n4/2023-09-ondo/blob/main/contracts/rwaOracles/RWADynamicOracle.sol#L122-L127
https://github.com/code-423n4/2023-09-ondo/blob/main/contracts/rwaOracles/RWADynamicOracle.sol#L117-L118


# Vulnerability details

## Impact

The `RWADynamicOracle.simulateRange` function is used to simulate the derivation of the prices returned from the oracle, given a range and a timestamp. 

When using this function if we are simulating the first range, we must set `startTime` and `rangeStartPrice`. If we are simulating a range > 1st then `startTime` and `rangeStartPrice` values will be calculated internally. 

But the issue is, the `simulateRange` function does not check whether the provided input `endTime` (`The end time for the range to simulate`) is a valid value. 

As a result if the user provides a `endTime < startTime` or the `endTime < lastRange.end` (when we are simulating a range other than range 1) this will derive the wrong price inside the following for loop execution.

    for (uint256 i = 0; i < length + 1; ++i) {
      Range memory range = rangeList[(length) - i];
      if (range.start <= blockTimeStamp) {
        if (range.end <= blockTimeStamp) {
          return derivePrice(range, range.end - 1);
        } else {
          return derivePrice(range, blockTimeStamp);
        }
      }
    }

This is because the `range.end < range.start` which is wrong execution state, but the `for` loop will continue execution because  both the `if` statements shown below will pass, and provide a wrong derived price to the end user.

      if (range.start <= blockTimeStamp) {
        if (range.end <= blockTimeStamp) {

## Proof of Concept

```solidity
  function simulateRange(
    uint256 blockTimeStamp,
    uint256 dailyIR,
    uint256 endTime,
    uint256 startTime,
    uint256 rangeStartPrice
  ) external view returns (uint256 price) {
```

https://github.com/code-423n4/2023-09-ondo/blob/main/contracts/rwaOracles/RWADynamicOracle.sol#L104-L110

```solidity
      rangeList[length] = Range(
        lastRange.end,
        endTime,
        dailyIR,
        prevClosePrice
      );
```

https://github.com/code-423n4/2023-09-ondo/blob/main/contracts/rwaOracles/RWADynamicOracle.sol#L122-L127

```solidity
      uint256 trueStart = (rangeStartPrice * ONE) / dailyIR;
      rangeList[length] = Range(startTime, endTime, dailyIR, trueStart);
```

https://github.com/code-423n4/2023-09-ondo/blob/main/contracts/rwaOracles/RWADynamicOracle.sol#L117-L118

## Tools Used
Manual Review and VSCode

## Recommended Mitigation Steps

Hence it is recommended to add the following two checks in the `simulateRange` function to verify provided `endTime`is valid as it is done with the `RWADynamicOracle.overrideRange` function and `RWADynamicOracle.setRange` function.

    if (startTime >= endTime) revert InvalidRange();

    if (lastRange.end >= en
TWO DIFFERENT TRANSACTIONS CAN RESULT IN THE SAME `txnHash` VALUE THUS BREAKING THE APPROVAL PROCESS OF TRANSACTION MINTING
Audit: 2023-09-ondo-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-ondo/blob/main/contracts/bridge/DestinationBridge.sol#L108-L109
https://github.com/code-423n4/2023-09-ondo/blob/main/contracts/bridge/DestinationBridge.sol#L137-L140
https://github.com/code-423n4/2023-09-ondo/blob/main/contracts/bridge/DestinationBridge.sol#L90-L91


# Vulnerability details

## Impact

The `DestinationBridge._execute` is an internal function that is executed when contract is called by Axelar Gateway. The `_execute` function stores the `Transaction` struct in the `txnHashToTransaction` mapping as shown below:

    bytes32 txnHash = keccak256(payload);
    txnHashToTransaction[txnHash] = Transaction(srcSender, amt); 

The transaction hash `txnHash` is calculated by `keccak256(payload)` and the `payload` is an `abi encoded` value consisting of following variables.

    bytes32 version, address srcSender, uint256 amt, uint256 nonce

The issue here is that the two different `srcChains` with two different `srcAddr` contracts can end up providing the same `txnHash` if the above mentioned `version`, `srcSender`, `amt` and `nonce` are the same. The `_execute` function only restricts the same `srcAddr` to not to use the same `nonce` as shown below:

    if (isSpentNonce[chainToApprovedSender[srcChain]][nonce]) {
      revert NonceSpent();
    }

But the problem is if there are different `srcAddr` providing the same `payload` it will result into the same `txnHash`.

Hence there could be two transactions with the same transaction hash (`txnHash`). Hence the later transaction will override the `txnToThresholdSet[txnHash]` of the former transaction. As a result the approval process for transaction minting will be broken.

## Proof of Concept

```solidity
    bytes32 txnHash = keccak256(payload);
    txnHashToTransaction[txnHash] = Transaction(srcSender, amt);
```

https://github.com/code-423n4/2023-09-ondo/blob/main/contracts/bridge/DestinationBridge.sol#L108-L109

```solidity
        txnToThresholdSet[txnHash] = TxnThreshold(
          t.numberOfApprovalsNeeded,
          new address[](0)
        );
```

https://github.com/code-423n4/2023-09-ondo/blob/main/contracts/bridge/DestinationBridge.sol#L137-L140

```solidity
    (bytes32 version, address srcSender, uint256 amt, uint256 nonce) = abi
      .decode(payload, (bytes32, address, uint256, uint256));
```

https://github.com/code-423n4/2023-09-ondo/blob/main/contracts/bridge/DestinationBridge.sol#L90-L91

## Tools Used
Manual Review and VSCode

## Recommended Mitigation Steps

Hence it is recommended to include the `srcChain` and the `srcAddr` in the payload as well which is getting hashed to calculate the `txnHash`. By doing so different transactions coming from different `srcChains` and `srcAddr` will not result into the same `txnHash`. Hence the approval process for transaction minting via the bridge will successfully execute.


## Assessed type

Other
Burner admin can't rUSDY.burn tokens from any account
Audit: 2023-09-ondo-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-ondo/blob/47d34d6d4a5303af5f46e907ac2292e6a7745f6c/contracts/usdy/rUSDY.sol#L672


# Vulnerability details

The natspec documentation for the [rUSDY.burn](https://github.com/code-423n4/2023-09-ondo/blob/47d34d6d4a5303af5f46e907ac2292e6a7745f6c/contracts/usdy/rUSDY.sol#L667C14-L667C71) function contains a statement that reads: `Admin burn function for burning rUSDY tokens from any account`, which is inaccurate.

## Proof-of-Concept
This function is intended for use in situations where a user possesses USDY but is legally prohibited from owning it.
If a user is blocked, sanctioned, or not on the allowlist, then an account with the BURNER_ROLE will not be able to burn the user's tokens.

Include the following mock contract and test in the `Test_rUSDY` to examine it:
```solidity
	contract BlockListMock {
  function isBlocked(address) external pure returns (bool) {
    return true;
  }
}

function test_rUSDY_adminCannotBurnFromBlocklistedAccount()
    public
    dealAndWrapAlice
  {
    vm.startPrank(guardian);
    BlockListMock blocklistContract = new BlockListMock();
    rUSDYToken.setBlocklist(address(blocklistContract));
    assertEq(address(rUSDYToken.blocklist()), address(blocklistContract));

    vm.expectRevert("rUSDY: 'sender' address blocked");
    rUSDYToken.burn(alice, 100e18);
  }
```

## Impact
This does not align with the documentation, and the tokens that belonged to this user will be locked. However, any other users who are on the allowlist, not blocked, or sanctioned can have their tokens burned.

## Tools Used
Manual

## Recommended Mitigation Steps
Please contemplate the possibility of enabling the burning of tokens exclusively from blocked or sanctioned accounts.


## Assessed type

Context