xanoutas/solidity-security-findings huggingface dataset:
Using `exchangeRateStored()` leads to an understatement of the `cToken` value
Audit: 2023-07-amphora-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-07-amphora/blob/daae020331404647c661ab534d20093c875483e1/core/solidity/contracts/periphery/oracles/CTokenOracle.sol#L38-L53


# Vulnerability details

## Impact

Current implementation of the `_get()` function means that the returned `cToken` value would be understated. Note that if this particular CToken isn't widely used, the `exchangeRateStored()` would easily be staled more significantly.

## Proof of Concept

Take a look at [CTokenOracle.sol#L38-L53](https://github.com/code-423n4/2023-07-amphora/blob/daae020331404647c661ab534d20093c875483e1/core/solidity/contracts/periphery/oracles/CTokenOracle.sol#L38-L53)

```solidity
  /// @notice returns the price with 18 decimals without any state changes
  /// @dev some oracles require a state change to get the exact current price.
  ///      This is updated when calling other state changing functions that query the price
  /// @return _price the current price
  function peekValue() public view override returns (uint256 _price) {
    _price = _get();
  }

  /// @notice The current reported value of the oracle
  /// @return _value The current value
  //@audit-issue use exchangeRateCurrent() instead
  function _get() internal view returns (uint256 _value) {
    uint256 _exchangeRate = cToken.exchangeRateStored();
    uint256 _currentValue = anchoredViewUnderlying.peekValue();

    _value = (_currentValue * _exchangeRate) / div;
  }
```

As seen the `_get()` function is designed to calculate the current value of the oracle. This calculation is carried out by fetching the stored exchange rate from the `cToken` contract.

This implementation, however leads to an incorrect value due to the fact that it relies on the [exchangeRateStored()](https://github.com/compound-finance/compound-protocol/blob/a3214f67b73310d547e00fc578e8355911c9d376/contracts/CToken.sol#L279-L286) function instead of the [exchangeRateCurrent()](https://github.com/compound-finance/compound-protocol/blob/a3214f67b73310d547e00fc578e8355911c9d376/contracts/CToken.sol#L270-L277), do note that both functions are readily available and the latter actually provides the up to date exchange rate since it first accrues interest and then returns price, implementation of both functions can be seen [here](https://github.com/compound-finance/compound-protocol/blob/a3214f67b73310d547e00fc578e8355911c9d376/contracts/CToken.sol#L270-L286)

```solidity
    /**
     * @notice Accrue interest then return the up-to-date exchange rate
     * @return Calculated exchange rate scaled by 1e18
     */
    function exchangeRateCurrent() override public nonReentrant returns (uint) {
        accrueInterest();
        return exchangeRateStored();
    }


    /**
     * @notice Calculates the exchange rate from the underlying to the CToken
     //@audit
     * @dev This function does not accrue interest before calculating the exchange rate
     * @return Calculated exchange rate scaled by 1e18
     */
    function excha
ChainlinkTokenOracleRelay.sol produces inaccurate token pricing
Audit: 2023-07-amphora-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-07-amphora/blob/daae020331404647c661ab534d20093c875483e1/core/solidity/contracts/periphery/oracles/ChainlinkTokenOracleRelay.sol#L44-L52


# Vulnerability details


## Impact

The `ChainlinkTokenOracleRelay` contract, as explicitly stated from comments in the code is designed to serve as an oracle for tokens that lack a direct USD trading pair but do possess a wETH/ETH pair. However the contract calculation of the token's price is entirely a different thing.

## Proof of Concept

Take a look at the [`_get()`](https://github.com/code-423n4/2023-07-amphora/blob/daae020331404647c661ab534d20093c875483e1/core/solidity/contracts/periphery/oracles/ChainlinkTokenOracleRelay.sol#L44-L52) function, which is designed to retrieve and return the current token price.

```solidity
function _get() internal view returns (uint256 _value) {
    uint256 _aggregatorPrice = AGGREGATOR.peekValue();
    uint256 _baseAggregatorPrice = BASE_AGGREGATOR.peekValue();

    _value = (_aggregatorPrice * _baseAggregatorPrice) / 1e18;
}
```

Say two tokens A and B, using the A/ETH and B/ETH chainlink feeds, this function performs this calculation as `(A/ETH) * (B/ETH) / 1e18`, where `A` and `B` are prices obtained from two separate Chainlink oraclest his computation results in a token's value being represented in terms of `AB/ETH^2`, instead of a _USD value_.

The function obtains prices from two different Chainlink oracles and performs a multiplication and division operation to derive the price. However, the price calculation does not correctly yield a USD value but instead presents the result in `AB/ETH^2`.

## Recommended Mitigation Steps

The price calculation for each token should instead be revised to `(A/ETH) * (ETH/USD)` & `(B/ETH) * (ETH/USD)` or something along these lines



## Assessed type

Context
Potential Failure to Handle Vault Creation Errors in mintVault Function
Audit: 2023-07-amphora-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-07-amphora/blob/daae020331404647c661ab534d20093c875483e1/core/solidity/contracts/core/VaultController.sol#L980


# Vulnerability details

## Impact
The `mintVault` function allows users to create new vaults. However, it fails to handle potential errors that may arise during the vault creation process. This omission may lead to unexpected behavior and leave users uninformed about the status of the vault creation.

## Proof of Concept
Within the `mintVault` function, after incrementing the vaultsMinted counter, the function proceeds to call the internal `_createVault` function to deploy a new vault. However, the return value of `_createVault`, which represents the address of the newly created vault, is not explicitly checked for validity.
```solidity

  function _createVault(uint96 _id, address _minter) internal virtual returns (address _vault) {
    _vault = address(VAULT_DEPLOYER.deployVault(_id, _minter));
  }
```

Possible Consequences:

If the `_createVault` function encounters an error during deployment, such as running out of gas, encountering an exception, or failing due to an issue in the VAULT_DEPLOYER contract, it will return the null address address(0).

As the return value is not checked, the function continues execution, assuming the vault creation was successful, which could lead to inconsistencies and unexpected behavior in the application.
Users will not be aware of the failure, as no error messages or exceptions are raised, potentially causing confusion and difficulty in diagnosing the issue.

## Tools Used
Manual Review


## Recommended Mitigation Steps
To mitigate this issue, it is crucial to add a check to verify the return value of _createVault before proceeding with the rest of the mintVault function. If the return value is equal to address(0), the function should revert the transaction, signaling the failure to create the vault and providing a meaningful error message. This way, users will be properly informed in case of any issues during the vault creation process.

```solidity
function mintVault() public override whenNotPaused returns (address _vaultAddress) {
    // increment minted vaults
    vaultsMinted += 1;
    // mint the vault itself, deploying the contract
    _vaultAddress = _createVault(vaultsMinted, _msgSender());

    require(_vaultAddress != address(0), "Vault creation failed");

    // add the vault to our system
    vaultIdVaultAddress[vaultsMinted] = _vaultAddress;
    // push new vault ID onto mapping
    walletVaultIDs[_msgSender()].push(vaultsMinted);

    // emit the event
    emit NewVault(_vaultAddress, vaultsMinted, _msgSender());
}
```





## Assessed type

Other
Old vote value is used during casting of proposal
Audit: 2023-07-amphora-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-07-amphora/blob/main/core/solidity/contracts/governance/GovernorCharlie.sol#L541


# Vulnerability details

## Impact
When a user casts a vote for a proposal, the internal function `_castVoteInternal` is executed, where their voting power is obtained from the block when the proposal is started.

`uint96 _votes = amph.getPriorVotes(_voter, _proposal.startBlock);`

The normal voting period for the proposal is `40_320` blocks. During this period, every user can vote for this proposal. At any time, votes can be transferred between users. If our user, Bob, casts a vote for this proposal at the block equal to `_proposal.startBlock + 40_000`, it is not certain that he will have the same amount of voting power as when the proposal was started at `_proposal.startBlock.`. If he transferred all votes to Alice in block `_proposal.startBlock + 1`, he will have 0 voting power when he casts vote for the proposal, but his old voting power will be used.

## Proof of Concept
https://docs.openzeppelin.com/contracts/4.x/api/governance#Votes-delegates-address-

## Tools Used
Manual review 

## Recommended Mitigation Steps
Use current amount of votes when users cast vote for a proposal.


Make following changes:

```diff
-uint96 _votes = amph.getPriorVotes(_voter, _proposal.startBlock);
+uint96 _votes = amph.getVotes(_voter); // Returns the current amount of votes that account has.
```


## Assessed type

Governance
`latestRoundData()` has no check for round completeness
Audit: 2023-07-amphora-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-07-amphora/blob/daae020331404647c661ab534d20093c875483e1/core/solidity/contracts/periphery/oracles/ChainlinkStalePriceLib.sol#L12


# Vulnerability details

## Impact
Lack of check for round completeness could lead to stale prices and wrong price return value, or outdated price. The functions that rely on accurate price feed might not work as expected and can lead to loss of funds.

If there is a problem with chainlink starting a new round and finding consensus on the new value for the oracle (e.g. chainlink nodes abandon the oracle, chain congestion, vulnerability/attacks on the chainlink system) consumers of this contract may continue using outdated stale data (if oracles are unable to submit no new round is started).

This could lead to stale prices and wrong price return value, or outdated price.

As a result, the functions rely on accurate price feed might not work as expected, sometimes can lead to fund loss. The impacts vary and depends on the specific situation like the following:

- incorrect liquidation
     - some users could be liquidated when they should not
     - no liquidation is performed when there should be
- wrong price feed
     - causing inappropriate loan being taken, beyond the current collateral factor
     - too low price feed affect normal borrow

## Proof of Concept
The oracle wrapper getOraclePrice() call out to an oracle with latestRoundData() to get the price of some token. Although the returned timestamp check has been noted by the bot finding, there is no check for round completeness.

According to Chainlink's documentation, this function does not error if no answer has been reached but returns 0 or outdated round data. The external Chainlink oracle, which provides index price information to the system, introduces risk inherent to any dependency on third-party data sources. For example, the oracle could fall behind or otherwise fail to be maintained, resulting in outdated data being fed to the index price calculations. Oracle reliance has historically resulted in crippled on-chain systems, and complications that lead to these outcomes can arise from things as simple as network congestion.




```
‎core/solidity/contracts/periphery/oracles/ChainlinkStalePriceLib.sol‎
  /// @notice Returns the current price from the aggregator
  function getCurrentPrice(AggregatorV2V3Interface _aggregator) internal view returns (uint256 _price) {
    (, int256 _answer,,,) = _aggregator.latestRoundData();
    if (_answer <= 0) revert Chainlink_NegativePrice();
    _price = uint256(_answer);
  }
```
```
‎core/solidity/contracts/periphery/oracles/ChainlinkOracleRelay.sol‎
  /// @notice Returns true if the price is stale
  /// @return _stale True if the price is stale
  function isStale() external view returns (bool _stale) {
    (,,, uint256 _updatedAt,) = _AGGREGATOR.latestRoundData();
    if (block.timestamp > _updatedAt + stalePriceDelay) _stale = true;
  }
```

## Reference
Chainl
wrong underlying token set in the `CTokenOracle` contract
Audit: 2023-07-amphora-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-07-amphora/blob/main/core/solidity/contracts/periphery/oracles/CTokenOracle.sol#L34-L35


# Vulnerability details

## Impact

In the `CTokenOracle` contract, the underlying token is set in the constructor with the internal function `_setUnderlying` and cannot be changed after, there is an error in the constructor as the `_cToken` address is set as underlying token instead of the true underlying token address `_underlying`. 

If this error is not noticed at the deployment of the `CTokenOracle` contract, it will cause the oracle to not work correctly and return wrong (incorrect) prices which will negatively impact the protocol functions that rely on accurate oracle prices. 

## Proof of Concept

The issue is occurring in the constructor of the `CTokenOracle` contract below :

```solidity
constructor(
    address _cToken,
    IOracleRelay _anchoredViewUnderlying
) OracleRelay(OracleType.Chainlink) {
    cToken = ICToken(_cToken);
    anchoredViewUnderlying = _anchoredViewUnderlying;

    // If underlying is ETH, decimals are 18, if not, get the decimals from the underlying
    uint256 _underlyingDecimals = cETH_ADDRESS == _cToken
        ? 18
        : IERC20Metadata(cToken.underlying()).decimals();

    // Save the divisor to convert the underlying to the cToken
    div = 10 ** (18 + _underlyingDecimals - cToken.decimals());

    // If the underlying is different, revert
    address _underlying = cETH_ADDRESS == _cToken
        ? wETH_ADDRESS
        : cToken.underlying();
    if (_underlying != anchoredViewUnderlying.underlying())
        revert OracleRelay_DifferentUnderlyings();

    // Set the underlying address
    // @audit should be _underlying not _cToken 
    _setUnderlying(_cToken);
}
```

As you can see from the code above, the address `_cToken` is given to the `_setUnderlying` function instead of the true underlying token address represented by the address `_underlying`.

Because the `_setUnderlying` function is an internal function and the `CTokenOracle` contract does not contain another public/external function to change the underlying token address after the deployment, this means that the incorrect underlying token address can never be changed in the future and this error will cause the oracle to return wrong prices which will impact the protocol functions that rely on accurate oracle prices in their logic.

## Tools Used

Manual review

## Recommended Mitigation Steps

To fix this issue, the correct underlying token address should be set in the constructor as follows : 

```solidity
constructor(
    address _cToken,
    IOracleRelay _anchoredViewUnderlying
) OracleRelay(OracleType.Chainlink) {
    cToken = ICToken(_cToken);
    anchoredViewUnderlying = _anchoredViewUnderlying;

    // If underlying is ETH, decimals are 18, if not, get the decimals from the underlying
    uint256 _underlyingDecimals = cETH_ADDRESS == _cToken
        ? 18
        : IERC20Metadata(cToken.underlying()
No Storage Gap for Upgradeable Contract Might Lead to Storage Slot Collision
Audit: 2023-07-amphora-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-07-amphora/blob/daae020331404647c661ab534d20093c875483e1/core/solidity/contracts/core/Vault.sol#L1


# Vulnerability details

## Impact
For upgradeable contracts, there must be storage gap to "allow developers to freely add new state variables in the future without compromising the storage compatibility with existing deployments" (quote OpenZeppelin). Otherwise it may be very difficult to write new implementation code. Without storage gap, the variable in child contract might be overwritten by the upgraded base contract if new variables are added to the base contract. This could have unintended and very serious consequences to the child contracts, potentially causing loss of user fund or cause the contract to malfunction completely.

Refer to the bottom part of this article: https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable

## Proof of Concept

following contract is intended to be upgradeable in the code base.

vault.sol

However, it doesn't contain storage gap. The storage gap is essential for upgradeable contract because "It allows us to freely add new state variables in the future without compromising the storage compatibility with existing deployments". Refer to the bottom part of this article:

https://docs.openzeppelin.com/contracts/3.x/upgradeable

If the contract inheriting the base contract contains additional variable, then the base contract cannot be upgraded to include any additional variable, because it would overwrite the variable declared in its child contract. This greatly limits contract upgradeability.

```
## Tools Used
Manual review

## Recommended Mitigation Steps
Recommend adding appropriate storage gap at the end of upgradeable contracts such as the below. Please reference OpenZeppelin upgradeable contract templates.
```solidity
uint256[50] private __gap;
```


## Assessed type

Other
Executed proposals can be queued again and re-executed .
Audit: 2023-07-amphora-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-07-amphora/blob/main/core/solidity/contracts/governance/GovernorCharlie.sol#L541


# Vulnerability details

## Impact 
AnyOne can vote even if they have no votes !

## Proof of Concept
when casting a vote  the `function _castVoteInternal` doesnot check if the voter has votes or not . `function _castVoteInternal` only reverts if 
1 .the proposal isn't active 
2 . support > 2 or
3 . if the user has already voted.

 This will let any user to vote which can lead to unexpected situations . 

```solidity 
  function _castVoteInternal(
    address _voter,
    uint256 _proposalId,
    uint8 _support
  ) internal returns (uint96 _numberOfVotes) {
    if (state(_proposalId) != ProposalState.Active) revert GovernorCharlie_VotingClosed();
    if (_support > 2) revert GovernorCharlie_InvalidVoteType();
    Proposal storage _proposal = proposals[_proposalId];
    Receipt storage _receipt = proposalReceipts[_proposalId][_voter];
    if (_receipt.hasVoted) revert GovernorCharlie_AlreadyVoted();
    uint96 _votes = amph.getPriorVotes(_voter, _proposal.startBlock);  //<--------here , no check for 0 votes 

    if (_support == 0) _proposal.againstVotes = _proposal.againstVotes + _votes;
    else if (_support == 1) _proposal.forVotes = _proposal.forVotes + _votes;
    else if (_support == 2) _proposal.abstainVotes = _proposal.abstainVotes + _votes;

    _receipt.hasVoted = true;
    _receipt.support = _support;
    _receipt.votes = _votes;

    _numberOfVotes = _votes;
  }
 
```

A similar finding can be found [here](https://solodit.xyz/issues/m-5-castvote-can-be-called-by-anyone-even-those-without-votes-sherlock-3d-frankenpunks-frankendao-git) .

## Tools Used
Manual Review 

## Recommended Mitigation Steps
Add this requirement ,  `if (_votes == 0) revert NoVotes(); `







## Assessed type

Governance
`MAX_SUPPLY` not checked when `USDA._mint` is called
Audit: 2023-07-amphora-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-07-amphora/blob/main/core/solidity/contracts/core/USDA.sol#L167-L178


# Vulnerability details

## Impact

In the USDA contract the variable representing the total supply `_totalSupply` has a maximum value set in the `UFragments` contract as `MAX_SUPPLY = 2 ** 128 - 1`, but when the function `USDA._mint` is called (by one of the functions : mint, _deposit, vaultControllerMint) it doesn't check that the new `_totalSupply` is indeed less than `MAX_SUPPLY` before minting new tokens, hence the total supply will surpass the limit set at some point in the future which can results in some problems for the protocol.

## Proof of Concept

The issue is occurring in the `_mint` function below :

```solidity
function _mint(address _target, uint256 _amount) internal {
    uint256 __gonsPerFragment = _gonsPerFragment;
    // the gonbalances of the sender is in gons, therefore we must multiply the deposit amount, which is in fragments, by gonsperfragment
    _gonBalances[_target] += _amount * __gonsPerFragment;
    
    // @audit MAX_SUPPLY not checked before incrementing
    // total supply is in fragments, and so we add amount
    _totalSupply += _amount;
    // and totalgons of course is in gons, and so we multiply amount by gonsperfragment to get the amount of gons we must add to totalGons
    _totalGons += _amount * __gonsPerFragment;
    // emit both a mint and transfer event
    emit Transfer(address(0), _target, _amount);
    emit Mint(_target, _amount);
}
```

As you can see from the code above, when the `_mint` function is called it does increment the value of `_totalSupply` immediately without checking if it will surpass the maximum supply amount set in the `UFragments` contract :

```solidity
// MAX_SUPPLY = maximum integer < (sqrt(4*_totalGons + 1) - 1) / 2
uint256 public MAX_SUPPLY; // = type(uint128).max; // (2^128) - 1
```

```solidity
MAX_SUPPLY = 2 ** 128 - 1;
```

In the USDA contract, both internal functions `_mint` and `_donation` are used to increment the value of `_totalSupply` when they are called by others public/external functions. The function `_donation` does perform a check to ensure that `_totalSupply` is always less than `MAX_SUPPLY`, but as we saw above the `_mint` function does not do the same, this will for sure cause the `_totalSupply` to surpass the maximum value at some point in the future which can in return result in some problems for the protocol as the logic parameters values will be out of range.

## Tools Used

Manual review

## Recommended Mitigation Steps

To fix this issue, i recommend to add a check in the `_mint` function to ensure that the value of `_totalSupply` is always less than the maximum value.

The function should be modified as follows : 

```solidity
function _mint(address _target, uint256 _amount) internal {
    uint256 __gonsPerFragment = _gonsPerFragment;
    // the gonbalances of the sender is in gons, therefore we must multiply the deposit amount,
Logic Error in Fee Limit Check
Audit: 2023-07-amphora-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-07-amphora/blob/main/core/solidity/contracts/core/VaultController.sol#L426


# Vulnerability details

## Impact
The logic will fail when this function be called with the exact `MAX_INIT_BORROWING_FEE` since the function is using `>=`. Should be greater >.

## Proof of Concept

https://github.com/code-423n4/2023-07-amphora/blob/main/core/solidity/contracts/core/VaultController.sol#L425C1-L431C4

```solidity
  function changeInitialBorrowingFee(uint192 _newBorrowingFee) external override onlyOwner {
    if (_newBorrowingFee >= MAX_INIT_BORROWING_FEE) revert VaultController_FeeTooLarge();
    uint192 _oldBorrowingFee = initialBorrowingFee;
    initialBorrowingFee = _newBorrowingFee;

    emit ChangedInitialBorrowingFee(_oldBorrowingFee, _newBorrowingFee);
  }
```


## Tools Used
Manual Review

## Recommended Mitigation Steps
Change it to > rather than >=


## Assessed type

Invalid Validation