xanoutas/solidity-security-findings huggingface dataset:
coinswap pool can be DoS
Audit: 2024-05-canto-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-canto/blob/d1d51b2293d4689f467b8b1c82bba84f8f7ea008/canto-main/x/coinswap/keeper/keeper.go#L168


# Vulnerability details

## Impact
DoS attack makes functions in coinswap unusable

## Proof of Concept
In general, if anyone is running in the array and an attacker adds a large number of elements to the array, it will consume a large amount of gas while traversing the array, and the related function will fail to execute due to insufficient gas.

The `AddLiquidity` `RemoveLiquidity` `calculateWithExactInput` in `coinswap` will pass
`k.GetPoolBalances` The banlance of all tokens in the pool.

k.GetPoolBalances -> k.bk.GetAllBalances


Let's take a look at `bk.GetAllBalances`, which iterate through the balance(IterateAccountBalances) of all tokens in the pool and then add them to an array and sort them.

https://github.com/cosmos/cosmos-sdk/blob/d21620d1280538ddb1129af4979d62878850ff99/x/bank/keeper/view.go#L99

```go
func (k BaseViewKeeper) GetAllBalances(ctx context.Context, addr sdk.AccAddress) sdk.Coins {
	balances := sdk.NewCoins()
	k.IterateAccountBalances(ctx, addr, func(balance sdk.Coin) bool {
		balances = balances.Add(balance)
		return false
	})

	return balances.Sort()
}
```

sdk.NewCoins() returns Coins, Coins is an array:
https://github.com/cosmos/cosmos-sdk/blob/d21620d1280538ddb1129af4979d62878850ff99/types/coin.go#L191


When `AddLiquidity` is in the coinswap module, `k.CreatePool` is called to create a pool if the pool does not exist,

```
    pool = k.CreatePool(ctx, msg.MaxToken.Denom)
```

An attacker can create a large number of tokens with `AddLiquidity` (or perhaps in other ways) 
and then send the balance to the target pool to be attacked.

When other users call functions in `coinswap`, the execution fails because the number of balances arrays returned by GetPoolBalances is too large, consumes too much gas, or the transaction execution failed because of insufficient gas.

## Tools Used
vscode, manual

## Recommended Mitigation Steps
Do not use` bk.GetAllBalances` to obtain the balances of all tokens, but only the balances of one token in the pool at a time.


## Assessed type

DoS
Govshuttle module does not register its transaction MsgServer
Audit: 2024-05-canto-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-canto/blob/d1d51b2293d4689f467b8b1c82bba84f8f7ea008/canto-main/x/govshuttle/module.go#L127


# Vulnerability details

The `x/govshuttle` module in `canto-main` defines and handles two messages that can be emitted by a governance proposal:
- [`MsgLendingMarketProposal`](https://github.com/code-423n4/2024-05-canto/blob/d1d51b2293d4689f467b8b1c82bba84f8f7ea008/canto-main/x/govshuttle/keeper/msg_server.go#L22)
- [`MsgTreasuryProposal`](https://github.com/code-423n4/2024-05-canto/blob/d1d51b2293d4689f467b8b1c82bba84f8f7ea008/canto-main/x/govshuttle/keeper/msg_server.go#L38)

However, because the module only registers the `QueryServer` (and not its MsgServer) in its `RegisterServices` function,
causing no message to be routed to its message server:

```go
func (am AppModule) RegisterServices(cfg module.Configurator) {
	types.RegisterQueryServer(cfg.QueryServer(), am.keeper)
}
```

If we compare this with another module that can handle messages, for example CSR, we see that this is the place for
registering the `MsgServer` where transactional messages are routed to:

```go
func (am AppModule) RegisterServices(cfg module.Configurator) {
	types.RegisterMsgServer(cfg.MsgServer(), am.keeper)
	types.RegisterQueryServer(cfg.QueryServer(), am.keeper)
}
```

## Impact
Successful governance actions that include a `LendingMarketProposal` or `TreasuryProposal` will fail to execute because
no handler is provided for them.

## Proof of Concept
To reproduce the issue it is sufficient to create and approve a proposal among the affected ones.

## Tools Used
Code review

## Recommended Mitigation Steps
Consider adding a `RegisterMsgServer` call in the `x/govshuttle` `RegisterService` callback.









## Assessed type

Other
Incomplete "cosmos.msg.v1.signer" protobuf annotation causes the "MsgSwapOrder" message to fail
Audit: 2024-05-canto-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-canto/blob/d1d51b2293d4689f467b8b1c82bba84f8f7ea008/canto-main/proto/canto/coinswap/v1/tx.proto#L97


# Vulnerability details

Before Cosmos SDK v0.50, the `GetSigners` method was used to specify what addresses are required to sign a transactional
message. With [Cosmos SDK v0.50 this method has been dropped](https://github.com/cosmos/cosmos-sdk/blob/release/v0.50.x/UPGRADING.md#all)
and replaced with the declarative `cosmos.msg.v1.signer` protobuf tag.

This tag was applied by the team correctly in most places, however, for [MsgSwapOrder](https://github.com/code-423n4/2024-05-canto/blob/d1d51b2293d4689f467b8b1c82bba84f8f7ea008/canto-main/proto/canto/coinswap/v1/tx.proto#L97), the annotation points to a nested `Input` message, which however [does not have a `cosmos.msg.v1.signer` to
  resolve the signer indirectly](https://github.com/code-423n4/2024-05-canto/blob/d1d51b2293d4689f467b8b1c82bba84f8f7ea008/canto-main/proto/canto/coinswap/v1/coinswap.proto#L13):

```protobuf
message MsgSwapOrder {
  option (cosmos.msg.v1.signer) = "input";
  option (amino.name) = "canto/MsgSwapOrder";

  Input input = 1 [ (gogoproto.nullable) = false ];
  Output output = 2 [ (gogoproto.nullable) = false ];
  int64 deadline = 3;
  bool is_buy_order = 4 [ (gogoproto.moretags) = "yaml:\"is_buy_order\"" ];
}
```

```protobuf
message Input {
  string address = 1;
  cosmos.base.v1beta1.Coin coin = 2 [ (gogoproto.nullable) = false ];
}
```

## Impact
The messages `MsgSwapOrder` will always fail on the Canto node.

## Proof of Concept
The issue can be verified by adding the following test in the `app` folder:

<details>
  <summary>canto-main/app/signers_test.go</summary>

```go
package app

import (
  "cosmossdk.io/x/tx/signing"
  coinswapapi "github.com/Canto-Network/Canto/v7/api/canto/coinswap/v1"
  "github.com/cosmos/cosmos-sdk/codec/address"
  sdk "github.com/cosmos/cosmos-sdk/types"
  "github.com/stretchr/testify/assert"
  "github.com/stretchr/testify/require"
  "testing"
)

func TestMsgSwapOrderSigners(t *testing.T) {
  sw := coinswapapi.MsgSwapOrder{
    Input: &coinswapapi.Input{Address: "something"},
  }
  ctx, err := signing.NewContext(signing.Options{
    AddressCodec: address.Bech32Codec{
      Bech32Prefix: sdk.GetConfig().GetBech32AccountAddrPrefix(),
    },
    ValidatorAddressCodec: address.Bech32Codec{
      Bech32Prefix: sdk.GetConfig().GetBech32ValidatorAddrPrefix(),
    },
  })
  require.NoError(t, err)
  signers, err := ctx.GetSigners(&sw)

  require.NoError(t, err)
  assert.Equal(t, 1, len(signers))
  assert.Equal(t, "something", signers[0])
}

```

</details>

Its output is:

> no cosmos.msg.v1.signer option found for message canto.coinswap.v1.Input; use DefineCustomGetSigners to specify a custom getter

## Tools Used
Code review

## Recommended Mitigation Steps
Consider adding a `DefineCustomGetSigners` call in `app.go` for the x/coinswap module's `Input` message, as done for 
`MsgConvertERC20`.

A
Incorrect names provided in `RegisterConcrete` calls break LegacyAmino signing method
Audit: 2024-05-canto-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-canto/blob/d1d51b2293d4689f467b8b1c82bba84f8f7ea008/canto-main/proto/canto/coinswap/v1/tx.proto#L40
https://github.com/code-423n4/2024-05-canto/blob/d1d51b2293d4689f467b8b1c82bba84f8f7ea008/canto-main/proto/canto/coinswap/v1/tx.proto#L68
https://github.com/code-423n4/2024-05-canto/blob/d1d51b2293d4689f467b8b1c82bba84f8f7ea008/canto-main/proto/canto/coinswap/v1/tx.proto#L98
https://github.com/code-423n4/2024-05-canto/blob/d1d51b2293d4689f467b8b1c82bba84f8f7ea008/canto-main/proto/canto/coinswap/v1/tx.proto#L116
https://github.com/code-423n4/2024-05-canto/blob/d1d51b2293d4689f467b8b1c82bba84f8f7ea008/canto-main/proto/canto/csr/v1/tx.proto#L20
https://github.com/code-423n4/2024-05-canto/blob/d1d51b2293d4689f467b8b1c82bba84f8f7ea008/canto-main/proto/canto/erc20/v1/tx.proto#L133
https://github.com/code-423n4/2024-05-canto/blob/d1d51b2293d4689f467b8b1c82bba84f8f7ea008/canto-main/proto/canto/erc20/v1/tx.proto#L112
https://github.com/code-423n4/2024-05-canto/blob/d1d51b2293d4689f467b8b1c82bba84f8f7ea008/canto-main/proto/canto/inflation/v1/tx.proto#L26
https://github.com/code-423n4/2024-05-canto/blob/d1d51b2293d4689f467b8b1c82bba84f8f7ea008/canto-main/proto/canto/onboarding/v1/tx.proto#L26
https://github.com/code-423n4/2024-05-canto/blob/d1d51b2293d4689f467b8b1c82bba84f8f7ea008/ethermint-main/proto/ethermint/evm/v1/tx.proto#L173
https://github.com/code-423n4/2024-05-canto/blob/d1d51b2293d4689f467b8b1c82bba84f8f7ea008/ethermint-main/x/feemarket/types/codec.go#L41


# Vulnerability details


One of the breaking changes introduced with the Cosmos SDK v0.50.x upgrade is [a change in the codec used for Amino JSON
(de)serialization](https://github.com/cosmos/cosmos-sdk/blob/release/v0.50.x/UPGRADING.md#protobuf). To ensure the
new codec behaves as the abandoned one did, the team added `amino.name` tags to the `message` types defined in the Canto
modules' ".proto" files.

There are however many instances where these tags are inconsistent with the `RegisterConcrete` calls made by the
in-scope modules' `func (AppModuleBasic) RegisterInterfaces` functions, all summarized below:

#### Module coinswap:
- [MsgAddLiquidity's tag in protobuf](https://github.com/code-423n4/2024-05-canto/blob/d1d51b2293d4689f467b8b1c82bba84f8f7ea008/canto-main/proto/canto/coinswap/v1/tx.proto#L40) (`canto/MsgAddLiquidity`) does not match [the name registered in code](https://github.com/code-423n4/2024-05-canto/blob/d1d51b2293d4689f467b8b1c82bba84f8f7ea008/canto-main/x/coinswap/types/codec.go#L24) (`coinswap/coinswap/MsgSwapOrder`) 
- [MsgRemoveLiquidity's tag in protobuf](https://github.com/code-423n4/2024-05-canto/blob/d1d51b2293d4689f467b8b1c82bba84f8f7ea008/canto-main/proto/canto/coinswap/v1/tx.proto#L68) (`canto/MsgRemoveLiquidity`) does not match [the name registered in code](https://github.com/code-423n4/2024-05-canto/blob/d1d51b2293d4689f467b8b1c82bba84f8f7ea008/canto-main/x/coinswap/types/codec.go#L25) (`coinswap/coinswap/MsgAd
Token Claim Issue in Pool Contract: Improper Distribution Leaves Pool Empty
Audit: 2024-06-vultisig-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-06-vultisig/blob/cb72b1e9053c02a58d874ff376359a83dc3f0742/src/ILOPool.sol#L242-L260
https://github.com/Uniswap/v3-core/blob/d8b1c635c275d2a9450bd6a78f3fa2484fef73eb/contracts/UniswapV3Pool.sol#L489-L513


# Vulnerability details


## Impact

The `claim` function of the pool contract improperly forwards all remaining tokens to the fee taker after paying the first user, leaving no tokens in the pool for subsequent claims. This leads to a situation where the entire liquidity pool is drained on the first claim, making it impossible for other users to claim their tokens.

1. **Drained Pool**: Once the first user claims their tokens, the contract forwards the maximum amount of tokens requested to the fee taker, leaving the pool empty.
2. **Inability to Claim**: Other users are unable to claim their tokens because the pool no longer has any liquidity after the first claim.


## Proof of Concept

 Scenario: Improper Token Distribution in `claim` Function

1. **Token Collection**: When a user initiates a claim, the contract collects the maximum amount of tokens from the Uniswap V3 pool.

 ```solidity
   (uint128 amountCollected0, uint128 amountCollected1) = pool.collect(
       address(this),
       TICK_LOWER,
       TICK_UPPER,
       type(uint128).max,
       type(uint128).max
   );
 ```

The code block below is executed on uniswapv3pool

   **Reference Code:**
   ```solidity
   // Uniswap V3 Pool collect function
   function collect(
       address recipient,
       int24 tickLower,
       int24 tickUpper,
       uint128 amount0Requested,
       uint128 amount1Requested
   ) external override lock returns (uint128 amount0, uint128 amount1) {
       Position.Info storage position = positions.get(msg.sender, tickLower, tickUpper);
       amount0 = amount0Requested > position.tokensOwed0 ? position.tokensOwed0 : amount0Requested;
       amount1 = amount1Requested > position.tokensOwed1 ? position.tokensOwed1 : amount1Requested;
       if (amount0 > 0) {
           position.tokensOwed0 -= amount0;
           TransferHelper.safeTransfer(token0, recipient, amount0);
       }
       if (amount1 > 0) {
           position.tokensOwed1 -= amount1;
           TransferHelper.safeTransfer(token1, recipient, amount1);
       }
       emit Collect(msg.sender, recipient, tickLower, tickUpper, amount0, amount1);
   }
   ```

2. **User Payment**: The contract then transfers the needed Amount from the collected tokens to the user:

 ```solidity
   TransferHelper.safeTransfer(_cachedPoolKey.token0, ownerOf(tokenId), amountCollected0);
   TransferHelper.safeTransfer(_cachedPoolKey.token1, ownerOf(tokenId), amountCollected1);
 ```

3. **Fee Taker Payment**: Instead of sending the fees to the feetaker and returning the remaining tokens to the pool, the contract transfers all the leftover tokens to the fee taker:

 ```solidity
   address feeTaker = IILOManager(MANAGER).FEE_TAKER();
   TransferHelper.safeTransfer(_cachedPool
`feeTaker` can receive more than actual fees when `claim(...)` is called
Audit: 2024-06-vultisig-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-06-vultisig/blob/cb72b1e9053c02a58d874ff376359a83dc3f0742/src/ILOPool.sol#L205-L260


# Vulnerability details

## Impact
`feeTaker` could receive more than the actual fees when claim is called leading to a leak of value and loss of funds. 

## Proof of Concept
Users call `claim(...)` to collect their tokens associated with a `tokenId` . As explained below excess fees can be sent to the `feeTaker` due to the logic implementation of the `claim(...)` function.

```solidity

File: ILOPool.sol

205:   @>        (amount0, amount1) = pool.burn(TICK_LOWER, TICK_UPPER, liquidity2Claim);
206: 
207:             // get amount of token0 and token1 after deduct platform fee
208:   @>        (amount0, amount1) = _deductFees(amount0, amount1, _project.platformFee);
209: 
210:             bytes32 positionKey = PositionKey.compute(address(this), TICK_LOWER, TICK_UPPER);
211: 
212:             // calculate amount of fees that position generated
213:             (, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128, , ) = pool.positions(positionKey);
214:             uint256 fees0 = FullMath.mulDiv(
215:                                 feeGrowthInside0LastX128 - position.feeGrowthInside0LastX128,
216:                                 positionLiquidity,
217:                                 FixedPoint128.Q128
218:                             );
219:             
220:             uint256 fees1 = FullMath.mulDiv(
221:                                 feeGrowthInside1LastX128 - position.feeGrowthInside1LastX128,
222:                                 positionLiquidity,
223:                                 FixedPoint128.Q128
224:                             );
225: 
226:             // amount of fees after deduct performance fee
227:  @>         (fees0, fees1) = _deductFees(fees0, fees1, _project.performanceFee);
228: 
229:             // fees is combined with liquidity token amount to return to the user
230:             amount0 += fees0;
231:             amount1 += fees1;

SNIP
...

239: 
240:         }
241:         // real amount collected from uintswap pool
242:  @>     (uint128 amountCollected0, uint128 amountCollected1) = pool.collect(
243:             address(this),
244:             TICK_LOWER,
245:             TICK_UPPER,
246:             type(uint128).max,
247:             type(uint128).max
248:         );
249:         emit Collect(tokenId, address(this), amountCollected0, amountCollected1);
250: 
251:         // transfer token for user
252:  @>     TransferHelper.safeTransfer(_cachedPoolKey.token0, ownerOf(tokenId), amount0);
253:  @>     TransferHelper.safeTransfer(_cachedPoolKey.token1, ownerOf(tokenId), amount1);

...
256: 
257:         address feeTaker = IILOManager(MANAGER).FEE_TAKER();
258:         // transfer fee to fee taker
259:  @>     TransferHelper.safeTransfer(_cachedPoolKey.token0, feeTaker, amountCollected0-amount0);
260:  @>     TransferHelper.safeTransfer(_cachedPoolKey.token1, feeTa
`ILOPool::claim` incorrectly re-adds fees to the caller's rewards, which can cause users to receive rewards owed to others, or DoS due to underflow
Audit: 2024-06-vultisig-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-06-vultisig/blob/main/src/ILOPool.sol#L204-L205
https://github.com/code-423n4/2024-06-vultisig/blob/main/src/ILOPool.sol#L207-L208
https://github.com/code-423n4/2024-06-vultisig/blob/main/src/ILOPool.sol#L212-L227
https://github.com/code-423n4/2024-06-vultisig/blob/main/src/ILOPool.sol#L241-L248
https://github.com/code-423n4/2024-06-vultisig/blob/main/src/ILOPool.sol#L251-L260


# Vulnerability details

## Impact
Project admins can create multiple `ILOPools` for their project, which contains mechanism such as vesting, sale management, ERC721 integration, etc.

Users can earn rewards for the amount they invested by calling `ILOPool::claim()`. These rewards are calculated based off the `liquidity2claim`, which is the `amount of unlocked liquidity for the position`.

`IUniswapV3PoolActions::burn()` is called with the `liquidity2claim` amount, which burns the liquidity and returns tokens owed for the liquidity to the position, `amount0` and `amount1`, which represent the `RAISE_TOKEN` and `SALE_TOKEN`.

A call to `IUniswapV3PoolActions::collect()` must be made to actually receive these tokens owed.

After burning the liquidity and before the call to collect(), the protocol calculates fees owed to the project: `platform fee` and `performance fee`. These fees must be deducted from the `amount0` and `amount1` from the `burn()` call.

The problem is that the protocol assumes these fees are not included in the `amount0` and `amount1` accounting, and proceeds to add them (minus the amount owed to project) to the `amount0` and `amount1` owed to the caller.

Due to this error in accounting, the user will receive extra rewards from the `ILOPool`, or DoS may occur due to underflow/insufficient funds.

## Proof of Concept
[ILOPool.sol#L204-L205](https://github.com/code-423n4/2024-06-vultisig/blob/main/src/ILOPool.sol#L204-L205)
```javascript
// get amount of token0 and token1 that pool will return for us
(amount0, amount1) = pool.burn(TICK_LOWER, TICK_UPPER, liquidity2Claim);
```

The claimable liquidity is burned from the pool and the amount of tokens earned is returned. This amount includes the fees owed to the position as observered [here](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol#L379).

Once the liquidity is burned, `platform fees` are deducted from the amounts returned.

[ILOPool.sol#L207-L208](https://github.com/code-423n4/2024-06-vultisig/blob/main/src/ILOPool.sol#L207-L208)
```javascript
    // get amount of token0 and token1 after deduct platform fee
    (amount0, amount1) = _deductFees(amount0, amount1, _project.platformFee);
```

The performance fee is also calculated.

[ILOPool.sol#L212-L227)](https://github.com/code-423n4/2024-06-vultisig/blob/main/src/ILOPool.sol#L212-L227)
```javascript
        // calculate amount of fees that position generated
        (, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128, , ) = pool.positions(positionKey);
Position owner may not receive accumulated LP fees when they claim liquidity
Audit: 2024-06-vultisig-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-06-vultisig/blob/cb72b1e9053c02a58d874ff376359a83dc3f0742/src/ILOPool.sol#L242-L260


# Vulnerability details

## Impact
Position owner may not receive accumulated LP fees when they claims liquidity, if they are not the first one to claim since the last time the LP fees are collected.

## Proof of Concept
When user calls [claim()](https://github.com/code-423n4/2024-06-vultisig/blob/cb72b1e9053c02a58d874ff376359a83dc3f0742/src/ILOPool.sol#L184-L189) to claim liquidity, the unlocked liquidity combined with the accumulated LP fees are sent to the position owner.
The LP fees user will receive is calculated based on the position liquidity and deducted by the performance fee.
```
            (, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128, , ) = pool.positions(positionKey);
            uint256 fees0 = FullMath.mulDiv(
                                feeGrowthInside0LastX128 - position.feeGrowthInside0LastX128,
                                positionLiquidity,
                                FixedPoint128.Q128
                            );
            
            uint256 fees1 = FullMath.mulDiv(
                                feeGrowthInside1LastX128 - position.feeGrowthInside1LastX128,
                                positionLiquidity,
                                FixedPoint128.Q128
                            );


            // amount of fees after deduct performance fee
            (fees0, fees1) = _deductFees(fees0, fees1, _project.performanceFee);
```
After calculation, protocol then collect LP fees from Uniswap pool.
```
        (uint128 amountCollected0, uint128 amountCollected1) = pool.collect(
            address(this),
            TICK_LOWER,
            TICK_UPPER,
            type(uint128).max,
            type(uint128).max
        );
```
Then part of the fees combined with unlocked liquidity are sent to the claimer.
```
        // transfer token for user
        TransferHelper.safeTransfer(_cachedPoolKey.token0, ownerOf(tokenId), amount0);
        TransferHelper.safeTransfer(_cachedPoolKey.token1, ownerOf(tokenId), amount1);
```
The rest are sent to __FEE_TAKER__.
```
        // transfer fee to fee taker
        TransferHelper.safeTransfer(_cachedPoolKey.token0, feeTaker, amountCollected0-amount0);
        TransferHelper.safeTransfer(_cachedPoolKey.token1, feeTaker, amountCollected1-amount1);
```
The problem is that all the uncollected LP fees are collected by the time, if some other users call __claim()__, they won't receive LP fees because there are no uncollected fees left.
Consider the following case:
1. Both Alice and Bob own a ILO position;
2. After pool launched and some swaps, there are LP fees accumulated in the Uniswap pool;
3. Alice calls to claim, the LP fees are collected, part of the fees along with the unlocked liquidity are sent to Alice;
4. Later when Bob calls to claim, he only receive unlocked liquidity because the collected LP fees are 0.