Sablier

Sablier
DeFiFoundry
53,440 USDC
View results
Submission Details
Severity: low
Invalid

Admin that create campaigns shorter than the grace period can rug recipients

Summary

According to Sablier they want to give a 7 days window to the campaign creator to clawback their funds in case some of the details of the campaign is not correct (invalid merkle root, invalid recipient details etc.)

The problem campaigns can be created using any amount of days, including <= 7.

Vulnerability Details

function _hasGracePeriodPassed() internal view returns (bool) {
return _firstClaimTime > 0 && block.timestamp > _firstClaimTime + 7 days;
}
function clawback(address to, uint128 amount) external override onlyAdmin {
// Check: current timestamp is over the grace period and the campaign has not expired.
if (_hasGracePeriodPassed() && !hasExpired()) {
revert Errors.SablierV2MerkleLockup_ClawbackNotAllowed({
blockTimestamp: block.timestamp,
expiration: EXPIRATION,
firstClaimTime: _firstClaimTime
});
}
// Effect: transfer the tokens to the provided address.
ASSET.safeTransfer(to, amount);
// Log the clawback.
emit Clawback(admin, to, amount);
}

Above we can see that the admin will skip the expiration process whenever he uses a shorter campaign that lasts less than the grace period defined.

PoC

  1. Add the following code on v2-periphery -> test -> integration -> merkle-lockup -> lt -> clawback -> clawback.t.sol

function testClawback_whenCampaignLastsShorterThanGracePeriod_usersGetRugged() external whenCallerAdmin {
// Given
merkleLT = createMerkleLT(getBlockTimestamp() + 6 days);
deal({ token: address(dai), to: address(merkleLT), give: defaults.AGGREGATE_AMOUNT() });
vm.warp({ newTimestamp: getBlockTimestamp() + 1 seconds });
claimLT();
// When 5 day passes(1 day before the last campaign's day)
vm.warp({ newTimestamp: getBlockTimestamp() + 5 days });
// Then admin withdraw all the funds
test_Clawback(users.admin);
// But campaign is not yet expired
assertFalse(merkleLT.hasExpired(), "expired");
// The problem is that the grace period has not passed yet
assertFalse(merkleLT.hasGracePeriodPassed(), "grace period passed"); // ps: create a public function `hasGracePeriodPassed` calling the internal function and add it to `ISablierV2MerkleLockup` interface.
// Result: Recipients that were eligible to claim got rugged.
// admin got all the funds.
resetPrank({ msgSender: users.recipient2 });
try merkleLT.claim({
index: defaults.INDEX2(),
recipient: users.recipient2,
amount: defaults.CLAIM_AMOUNT(),
merkleProof: defaults.index2Proof()
}) {
assert(false);
} catch (bytes memory) {
// revert due to ERC20InsufficientBalance transferFrom: SablierV2MerkleLT -> LockupTranched
assert(true);
}
}

Impact

  • Admins can create campaigns that will lead users to think that they will be eligible to claim their assets, but they will be eventually rugged by the admin.

  • Probability and impact: High. As Sablier is a product that covers different kind of token allocation/distribution, there will be many cases that will fit the scenario above.

Tools Used

Manual Review & Foundry

Recommendations

There are different options:

  • Redefine the delay for the grace period when campaigns with shorter days are created.

  • Add a minimum value for creating campaigns. i.e: minimum 7 days.

  • Check for the percentage of the time passed after the first claim. i.e: grace period is 5% of the campaign's time after the first claim.

Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Known issue
Assigned finding tags:

Known - Contest Details

https://www.codehawks.com/contests/clvb9njmy00012dqjyaavpl44

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.