xanoutas/solidity-security-findings huggingface dataset:
Malicious user can be approver
Audit: 2023-09-ondo-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The first approver of every transaction that is added should always be Axelar Relayer but this is not entirely true.

```solidity
t.approvers.push(msg.sender); // in _approve function
```

Before sending of transaction to `DestinationBridge` the transaction should be approved by Axelar.

For one transaction to be approved, the following sequence of events should occur:
1. The Axelar Relayer calls the `execute(bytes input)` function of the Axelar Gateway contract [link](https://github.com/axelarnetwork/axelar-cgp-solidity/blob/main/contracts/AxelarGateway.sol#L495).
2. Subsequently, the `approveContractCall` function [link](https://github.com/axelarnetwork/axelar-cgp-solidity/blob/main/contracts/AxelarGateway.sol#L550) is called, where the following data is stored: `sourceChain`, `sourceAddress`, `contractAddress`, `payloadHash`, `sourceTxHash` and `sourceEventIndex`.
3. An `ContractCallApproved` event is emitted [link](https://github.com/axelarnetwork/axelar-cgp-solidity/blob/main/contracts/AxelarGateway.sol#L658-L670).

Example of the above behavior with a real transaction: [link to snowtrace](https://snowtrace.io/tx/0x81fb8aa0b75120c37ce268dd4bea6d3250ac7a8d8a6bcc8721bfdccfa8301d55)

When an event is emitted, the Axelar Relayer sends a new transaction to `AxelarExecutable`. The `DestinationBridge` contract inherits the `AxelarExecutable` contract and implements the `execute` function, which checks for the approval of the transaction in the Gateway contract. This approval is true because the explained behavior above has been fulfilled.

```solidity
    if (
      !gateway.validateContractCall(
        commandId,
        sourceChain,
        sourceAddress,
        payloadHash
      )
    ) revert NotApprovedByGateway();
```

This can easily be front-run by a malicious, blocked or sanctioned user, as he need to execute the same transaction with more gas to be prioritized. His transaction will bypass the approval check and `msg.sender` (malicious user) will be added as a part of the approvers of the transaction.


```solidity
_attachThreshold(amt, txnHash, srcChain);
_approve(txnHash); // Adding of first approver
_mintIfThresholdMet(txnHash);
```
[Link to codebase](https://github.com/code-423n4/2023-09-ondo/blob/main/contracts/bridge/DestinationBridge.sol#L111)

## PoC
Here is an example with a real transaction that demonstrates the approving behavior: [link](https://snowtrace.io/tx/0x81fb8aa0b75120c37ce268dd4bea6d3250ac7a8d8a6bcc8721bfdccfa8301d55). You can easily view the log of the event. Additionally, you can visit this [link](https://axelarscan.io/gmp/0x3a647ec91a204941f768b509aeaa809fe6672524ecf9d796bb1db2b632f2eabf:296) to explore the full pathway of Axelar's work.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Implement access control to ensure that the `execute` function can on
Rounding issues in rUSDY calculations
Audit: 2023-09-ondo-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-ondo/blob/main/contracts/usdy/rUSDY.sol#L468
https://github.com/code-423n4/2023-09-ondo/blob/main/contracts/usdy/rUSDY.sol#L451
https://github.com/code-423n4/2023-09-ondo/blob/main/contracts/usdy/rUSDY.sol#L676


# Vulnerability details

Conversion between shares and rUSDY amounts always round down in the rUSDY, which may not be the appropriate calculation in some cases.

## Impact

The `getSharesByRUSDY()` function present in the rUSDY contract can be used to convert between rUSDY tokens and shares.

https://github.com/code-423n4/2023-09-ondo/blob/main/contracts/usdy/rUSDY.sol#L388-L392

```solidity
388:   function getSharesByRUSDY(
389:     uint256 _rUSDYAmount
390:   ) public view returns (uint256) {
391:     return (_rUSDYAmount * 1e18 * BPS_DENOMINATOR) / oracle.getPrice();
392:   }
```

The implementation contains a division that, under integer arithmetic, will round down the resulting value. This rounding will cause different issues in different places of the rUSDY token implementation.

One of these issues is in the token transfer implementation. Both `transfer()` and `transferFrom()` calculate the needed shares using `getSharesByRUSDY()` and execute a movement of shares between both accounts:

https://github.com/code-423n4/2023-09-ondo/blob/main/contracts/usdy/rUSDY.sol#L463-L472

```solidity
463:   function _transfer(
464:     address _sender,
465:     address _recipient,
466:     uint256 _amount
467:   ) internal {
468:     uint256 _sharesToTransfer = getSharesByRUSDY(_amount);
469:     _transferShares(_sender, _recipient, _sharesToTransfer);
470:     emit Transfer(_sender, _recipient, _amount);
471:     emit TransferShares(_sender, _recipient, _sharesToTransfer);
472:   }
```

A rounded down value in the shares being transferred means that the recipient may not get the specified amount. There will be a slight difference due to the rounded down shares causing the recipient to actually receive less than specified.

Another issue is present in the `unwrap()` function. The implementation takes an amount of rUSDY tokens (`_rUSDYAmount`) and calculates the shares to burn using `getSharesByRUSDY()`. A rounded down value here means that the user may not be able to fully redeem the entirety of their shares.

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

```solidity
449:   function unwrap(uint256 _rUSDYAmount) external whenNotPaused {
450:     require(_rUSDYAmount > 0, "rUSDY: can't unwrap zero rUSDY tokens");
451:     uint256 usdyAmount = getSharesByRUSDY(_rUSDYAmount);
452:     if (usdyAmount < BPS_DENOMINATOR) revert UnwrapTooSmall();
453:     _burnShares(msg.sender, usdyAmount);
454:     usdy.transfer(msg.sender, usdyAmount / BPS_DENOMINATOR);
455:     emit TokensBurnt(msg.sender, _rUSDYAmount);
456:   }
```

Finally there is also a potential issue in `burn()`. Once again, `getSharesByRUSDY()` is used to calculate the amount of shares correspon
Overriding ranges in oracle may lead to gaps
Audit: 2023-09-ondo-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

The override feature in the RWADynamicOracle contract can lead to gaps in time without provided values.

## Impact

Already configured ranges in the oracle contract can be re-configured using the `overrideRange()` function. This function takes the index of the range to modify along with the new values to configure in the corresponding range:

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

```solidity
186:   function overrideRange(
187:     uint256 indexToModify,
188:     uint256 newStart,
189:     uint256 newEnd,
190:     uint256 newDailyIR,
191:     uint256 newPrevRangeClosePrice
192:   ) external onlyRole(DEFAULT_ADMIN_ROLE) {
193:     // Check that the ranges start and end time are less than each other
194:     if (newStart >= newEnd) revert InvalidRange();
195: 
196:     uint256 rangeLength = ranges.length;
197:     // Case 1: The range being modified is the first range
198:     if (indexToModify == 0) {
199:       // If the length of ranges is greater than 1,
200:       // Ensure that the newEnd time is not greater than the start time of the next range
201:       if (rangeLength > 1 && newEnd > ranges[indexToModify + 1].start)
202:         revert InvalidRange();
203:     }
204:     // Case 2: The range being modified is the last range
205:     else if (indexToModify == rangeLength - 1) {
206:       // Ensure that the newStart time is not less than the end time of the previous range
207:       if (newStart < ranges[indexToModify - 1].end) revert InvalidRange();
208:     }
209:     // Case 3: The range being modified is between first and last range
210:     else {
211:       // Ensure that the newStart time is less than the end time of the previous range
212:       if (newStart < ranges[indexToModify - 1].end) revert InvalidRange();
213:       // Ensure that the newEnd time is not greater than the start time of the next range
214:       if (newEnd > ranges[indexToModify + 1].start) revert InvalidRange();
215:     }
216: 
217:     // Update range
218:     if (indexToModify == 0) {
219:       uint256 trueStart = (newPrevRangeClosePrice * ONE) / newDailyIR;
220:       ranges[indexToModify] = Range(newStart, newEnd, newDailyIR, trueStart);
221:     } else {
222:       ranges[indexToModify] = Range(
223:         newStart,
224:         newEnd,
225:         newDailyIR,
226:         newPrevRangeClosePrice
227:       );
228:     }
229:     emit RangeOverriden(
230:       indexToModify,
231:       newStart,
232:       newEnd,
233:       newDailyIR,
234:       newPrevRangeClosePrice
235:     );
236:   }
```

While the implementation correctly validates that the new timestamps don't exceed the limits of the adjacent ranges (lines 197-215), there is nothing preventing these values from being lower than the adjacent timestamp, causing gaps in time. For examp
Chain support cannot be removed or cleared in bridge contracts
Audit: 2023-09-ondo-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-ondo/blob/main/contracts/bridge/SourceBridge.sol#L121-L129
https://github.com/code-423n4/2023-09-ondo/blob/main/contracts/bridge/DestinationBridge.sol#L234-L240


# Vulnerability details

# Chain support cannot be removed or cleared in bridge contracts

Due to how addresses are handled and stored in the configuration settings, it is not possible to remove chain support in both source and destination bridge contracts.

## Impact

The Axelar bridge service uses strings to represent addresses: messages sent to another chain need to specify its destination contract as a string. The protocol decided to follow the same representation and contract store addresses as strings as part of their configuration.

Chain support in the bridge contracts is then represented by associating the chain name with the address of the contract, stored as a string. This can be seen in the implementation of `setDestinationChainContractAddress()` for the SourceBridge contract, which stores the string, and the implementation of `addChainSupport()` in the DestinationBridge contract, which stores the hash of the string:

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

```solidity
121:   function setDestinationChainContractAddress(
122:     string memory destinationChain,
123:     address contractAddress
124:   ) external onlyOwner {
125:     destChainToContractAddr[destinationChain] = AddressToString.toString(
126:       contractAddress
127:     );
128:     emit DestinationChainContractAddressSet(destinationChain, contractAddress);
129:   }
```

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

```solidity
234:   function addChainSupport(
235:     string calldata srcChain,
236:     string calldata srcContractAddress
237:   ) external onlyOwner {
238:     chainToApprovedSender[srcChain] = keccak256(abi.encode(srcContractAddress));
239:     emit ChainIdSupported(srcChain, srcContractAddress);
240:   }
```

This also means that checks need to be done based on the stored representation. SourceBridge checks the length of the string, while DestinationBridge checks for a `bytes32(0)` value:

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

```solidity
68:     if (bytes(destContract).length == 0) {
69:       revert DestinationNotSupported();
70:     }
71: 
```

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

```solidity
96:     if (chainToApprovedSender[srcChain] == bytes32(0)) {
97:       revert ChainNotSupported();
98:     }
```

Note that this implies that there is no way to clear these settings and remove chain support. In the case of SourceBridge, any address sent to `setDestinationChainContractAddress()` will be converted to their string representation which will always have length greater than zero. For the DestinationBridge,
Approver could accidentally DoS their approval by executing the bridged message
Audit: 2023-09-ondo-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

Transaction approvals in the destination bridge are registered by address to avoid the same approver from approving it more than once. It is possible that a registered approver executes the message in the destination bridge accidentally including themselves in the list of approvers and preventing them from making the "real" approval.

## Impact

Approval semantics in the destination bridge are a bit confusing. Bridged messages grant an "automatic" approval when the message is being executed in the destination bridge:

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

```solidity
108:     bytes32 txnHash = keccak256(payload);
109:     txnHashToTransaction[txnHash] = Transaction(srcSender, amt);
110:     _attachThreshold(amt, txnHash, srcChain);
111:     _approve(txnHash);
112:     _mintIfThresholdMet(txnHash);
113:     emit MessageReceived(srcChain, srcSender, amt, nonce);
```

This means that any bridged message will get at least one automatic approval. This seems to be designed to avoid defining thresholds with a zero value for `numberOfApprovalsNeeded` to avoid any potential conflict with an uninitialized setting (since the default value for an integer is zero). This also means that messages that require an "actual" approval will need its threshold config to be set to (at least) 2 as the number of required approvals.

Approvals are of course checked for duplicates to avoid the same approver from approving the message multiple times:

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

```solidity
156:   function _approve(bytes32 txnHash) internal {
157:     // Check that the approver has not already approved
158:     TxnThreshold storage t = txnToThresholdSet[txnHash];
159:     if (t.approvers.length > 0) {
160:       for (uint256 i = 0; i < t.approvers.length; ++i) {
161:         if (t.approvers[i] == msg.sender) {
162:           revert AlreadyApproved();
163:         }
164:       }
165:     }
166:     t.approvers.push(msg.sender);
167:   }
```

Since bridged messages can be executed by anyone (the `execute()` function in AxelarExecutable is permissionless), this means that it could be executed by an approver, which would cause him to be added to the list of approvers due to the automatic call to `_approve()` in `_execute()`, locking him out of making the "real" approval.

## Proof of Concept

1. Approver calls `execute()` in DestinationBridge.
2. Approver tries to call `approve()` to grant their approval, but it will revert since the approver is already included in the list of approvers.

## Recommended Mitigation Steps

Avoid the confusing semantics by allowing the number of required approvals to be zero. This would allow to remove the automatic call to `_approve()` that is part of the implementation
Bridged tokens could be lost if sender transfer concurrently from multiple source chains
Audit: 2023-09-ondo-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

Bridged messages containing the same information coming from different source chains may clash in the destination bridge and lead to loss of tokens.

## Impact

Tokens are bridged by encoding some information that is sent as the payload of the bridged message through the Axelar service. The destination bridge then recovers this information and uses its hash as the key value to identify the transaction while it is under review by the approvers.

The SourceBridge contract encodes the current version, the sender, amount and current nonce as the payload of the message:

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

```solidity
79:     bytes memory payload = abi.encode(VERSION, msg.sender, amount, nonce++);
80: 
81:     _payGasAndCallContract(destinationChain, destContract, payload);
```

The DestinationBridge receives this payload and hashes it using `keccak256` that serves as the key of both mappings `txnHashToTransaction` (which holds the transaction information) and `txnToThresholdSet` (which holds the current state of approvals):

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

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

Note that the payload doesn't contain the source bridge address, and the mapping isn't scoped to the source chain, the key is just the hash of the payload. If an account wants to bridge the same amount of tokens from different source chains, which also happen to have by coincidence the same current nonce in their source bridge, both messages will end up having the same payload and the same value for their hash, causing a clash in these mappings.

Since it is likely that bridged messages will need confirmation by approvers (i.e. minting won't happen automatically in `_execute()`), the described will lead to loss of funds for the user: while the first transaction is pending to be executed as it is being reviewed by the approvers, the next transaction will overwrite all the data from the first, leading to information loss and preventing the minting of tokens from the first transaction that arrived. 

## Proof of Concept

User has the same amount of tokens in both source chain 1 (`SC1`) and source chain 2 (`SC2`) and wants to bridge them to the destination chain (`DC`). State of source bridge in both `SC1` and `SC2` currently contains the same nonce value.

1. User calls `burnAndCallAxelar(amount, DC)` in `SC1`.
2. `DC` receives message through `execute()`. This will hash the payload as `txnHash` and store this transaction in `txnHashToTransaction[txnHash]` while it is under review to be approved.
3. User calls `burnAndCallAxelar(amount, DC)` in `SC2`.
4. Again, `DC` receives message through `execute()`
`txnHash` collision from different source chains would override the under-approval-threshold transaction, causing loss of funds
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


# Vulnerability details

## Impact
The `DestinationBridge._execute`'s payload is only constructed based on __VERSION__, __sender__, __amount__ and __nonce__ which does not quarantee uniqueness between different source chains. This could cause transaction hash colissions. Given the `srcSender` is a big integrated protocol, the chances of `nonce` collision are very likely. When txnHash colides, `txnToThresholdSet` & `txnHashToTransaction` data from the pending tx would be silently overriden at lines 109-110.
```solidity
// Findings are labeled with '<= FOUND'
// File: contracts/bridge/DestinationBridge.sol
85:  function _execute(
86:    ...
108:    bytes32 txnHash = keccak256(payload);// <= FOUND
109:    txnHashToTransaction[txnHash] = Transaction(srcSender, amt);
110:    _attachThreshold(amt, txnHash, srcChain);
111:    _approve(txnHash);
112:    _mintIfThresholdMet(txnHash);
113:    ...
114:  }
```

As only one of the identical transactions would be successfully processed in `_mintIfThresholdMet`, and the other is overridden due to the collision of transaction hashes, the result is a loss of funds for the sender.
> Potential loss = ( number of identical transactions - 1 ) x amount

## Proof of Concept
1. **Scenario Setup**: Suppose there are two source chains, Chain A and Chain B, and a protocol P that wants to send $1000 worth of tokens from Chain A to Chain B using a function `burnAndCallAxelar` with `nonce = n`.

2. **Chain B Execution**: Axelar executes a corresponding transaction on Chain B in response to the `burnAndCallAxelar` function call. The transaction data is registered with a unique transaction hash `txnHash`.

3. **Pending Transaction**: While the transaction on Chain B is still pending and awaiting enough approvals, protocol P performs operations on Chain C, catching up from `nonce = n - m` to the current `nonce = n`. During this time, protocol P sends another `burnAndCallAxelar` transaction with the same payload to Chain B.

4. **Override Transaction Data**: Axelar repeats the process for the new transaction on Chain B, overriding the previously registered data associated with the same `txnHash`. 

5. **Execution on Chain B**: Eventually, enough approvals are met for the transaction on Chain B. As a result, tokens worth $1000 are minted on Chain B based on the overridden data. However, this action disregards the other $1000 worth of tokens that were part of the original transaction on Chain A.

6. **Loss of Funds**: The result is a loss of funds, as only one of the two identical transactions is successfully processed, and the other is overridden due to the collision of transaction hashes.

## Tools Used
Manual Review

## Recommended Mitigation Steps
To mitigate this issue, it is recommended to include the source chain name in the transaction hash calculation, making it unique throughout multiple chains. This way, co
Users can mint tokens on destination chain without paying any Gas fee on source chain
Audit: 2023-09-ondo-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-09-ondo/blob/main/contracts/bridge/SourceBridge.sol#L81-L106 https://github.com/axelarnetwork/axelar-cgp-solidity/blob/main/contracts/gas-service/AxelarGasService.sol#L175-L186


# Vulnerability details

## Impact
To burn amount tokens on source chain and mint on destination chain , the user calls the function `burnAndCallAxelar`

Inside this function , we burn some `amount` tokens from user.
And then pass our gas to the AxelerGasService through `_payGasAndCallContract`.

Inside `payGasForExpressCall`(although outside of scope but has impact on our in-scope contracts) ,
we have an unchecked safeTransferFrom call that sometimes can silently fail depending upon the implementation of the `Gas Token` used.

This will not cause any revert if the safeTransferFrom just silently fails and there is not transfer of Gas Tokens from user to the Gas Service contract.

The entire function and hence the transaction will get executed even if the user has not paid any amount of `Gas Token`s.


## Tools Used
Manual review
## Recommended Mitigation Steps
Try to check the balance of the contract after the call to Gas Service contract so that the protocol does not lose any funds !



## Assessed type

Token-Transfer