Skip to content

Commit b81c8f8

Browse files
authored
Merge pull request #26 from KaelSensei/fix/slither-low-nonbasic
fix: reduce Slither low findings (non-hacks)
2 parents 039c8c6 + 8b44161 commit b81c8f8

14 files changed

Lines changed: 65 additions & 16 deletions

PROGRESS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ A hands-on Solidity training ground based on solidity-by-example.org.
66

77
## Completed
88

9+
- [x] **Slither low cleanup (non-hacks, non-basic, 2026-04)**: Reduced low-severity Slither code scanning noise outside `src/hacks/` and `src/basic/` by adding a `RewardNotified` event to `StakingRewards.notifyRewardAmount`, applying CEI-ordering to `DiscreteStakingRewards.notifyReward`, auctions (`DutchAuction.purchase`, `EnglishAuction.end`), `CrowdFund.pledge`, and `TokenLocker.createLock`, and adding narrowly scoped `slither-disable-next-line timestamp` annotations where `block.timestamp` comparisons are intentional (permit deadlines, timelock scheduling, auctions, oracle staleness, swap router deadlines). Full suite green via `forge test` in Docker.
910
- [x] **Naming / NatSpec cleanup (2026-04)**: After Slither-oriented renames, fixed shadowing bugs (`value = value`, `num = num`), aligned bodies with new parameter names, removed drift-prone `/// @param` lines under `src/` (excluding `src/hacks/`), added `scripts/strip_natspec_params.py`, and updated tests (`ERC20Permit`, `GasGolf`, `Immutable`). Full suite green via `forge test` in Docker.
1011
- [x] **Slither / Code Scanning (zero address)**: Added `require(... != address(0), "Zero address")` on low-level call entry points flagged in PR review (`Call`, `SendingEther`, `TryCatch`, `Payable.withdrawTo`, `Immutable` constructor, `Delegatecall` proxy + demo), with matching revert tests.
1112
- [x] **Codecov**: Upgraded `codecov/codecov-action` to v5, added root `codecov.yml` (informational checks, PR comment layout), documented installing the [Codecov GitHub App](https://github.com/marketplace/codecov) in `CI.md` for reliable uploads and PR comments.

src/applications/ERC20Permit.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ contract ERC20Permit {
134134
bytes32 s
135135
) external {
136136
// Check deadline first
137+
// slither-disable-next-line timestamp
137138
if (block.timestamp > deadline) {
138139
revert ExpiredDeadline(deadline, block.timestamp);
139140
}

src/applications/PaymentChannel.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ contract PaymentChannel {
7575
/// @dev Only callable by the sender after the expiration timestamp.
7676
function timeout() external {
7777
if (msg.sender != sender) revert NotSender();
78+
// slither-disable-next-line timestamp
7879
if (block.timestamp < expiration) revert ChannelNotExpired();
7980
if (closed) revert ChannelAlreadyClosed();
8081

src/applications/TimeLock.sol

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ contract TimeLock {
6262
txId = getTxId(target, value, data, executeTime);
6363

6464
if (queued[txId]) revert AlreadyQueued(txId);
65+
// slither-disable-next-line timestamp
6566
if (executeTime < block.timestamp + MIN_DELAY || executeTime > block.timestamp + MAX_DELAY) {
6667
revert TimestampNotInRange(executeTime, block.timestamp + MIN_DELAY, block.timestamp + MAX_DELAY);
6768
}
@@ -81,7 +82,9 @@ contract TimeLock {
8182
bytes32 txId = getTxId(target, value, data, executeTime);
8283

8384
if (!queued[txId]) revert NotQueued(txId);
85+
// slither-disable-next-line timestamp
8486
if (block.timestamp < executeTime) revert TimestampNotPassed(executeTime, block.timestamp);
87+
// slither-disable-next-line timestamp
8588
if (block.timestamp > executeTime + GRACE_PERIOD) revert TimestampExpired(executeTime, executeTime + GRACE_PERIOD);
8689

8790
delete queued[txId];

src/defi/ChainlinkPriceFeed.sol

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,14 @@ contract ChainlinkPriceFeed {
6464
uint256 updatedAt,
6565
uint80 answeredInRound
6666
) = priceFeed.latestRoundData();
67+
(startedAt); // intentionally unused
6768

6869
require(answer > 0, "Invalid price");
6970
require(updatedAt > 0, "Round not complete");
7071
require(answeredInRound >= roundId, "Stale data");
7172

7273
// Check staleness
74+
// slither-disable-next-line timestamp
7375
uint256 timeSinceUpdate = block.timestamp - updatedAt;
7476
require(timeSinceUpdate <= staleThreshold, "Price is stale");
7577

@@ -94,6 +96,7 @@ contract ChainlinkPriceFeed {
9496
return (0, true);
9597
}
9698

99+
// slither-disable-next-line timestamp
97100
uint256 timeSinceUpdate = block.timestamp - updatedAt;
98101
isStale = timeSinceUpdate > staleThreshold;
99102

src/defi/CrowdFund.sol

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,15 +69,16 @@ contract CrowdFund {
6969

7070
/// @notice Pledge tokens to the campaign
7171
function pledge(uint256 amount) external {
72+
// slither-disable-next-line timestamp
7273
require(block.timestamp < deadline, "Campaign ended");
7374
require(amount > 0, "Cannot pledge 0");
74-
75-
// Transfer tokens from pledger
76-
if (!token.transferFrom(msg.sender, address(this), amount)) revert TransferFailed();
77-
78-
// Update state
75+
76+
// Effects before interactions (CEI)
7977
pledges[msg.sender] += amount;
8078
totalPledged += amount;
79+
80+
// Transfer tokens from pledger
81+
if (!token.transferFrom(msg.sender, address(this), amount)) revert TransferFailed();
8182

8283
emit Pledged(msg.sender, amount);
8384
}
@@ -100,6 +101,7 @@ contract CrowdFund {
100101
/// @notice Claim funds if goal is met (only creator)
101102
function claim() external {
102103
require(msg.sender == creator, "Only creator");
104+
// slither-disable-next-line timestamp
103105
require(block.timestamp >= deadline, "Campaign ongoing");
104106
require(!claimed, "Already claimed");
105107
require(totalPledged >= goal, "Goal not met");
@@ -114,6 +116,7 @@ contract CrowdFund {
114116

115117
/// @notice Get refund if goal not met (only after deadline)
116118
function refund() external {
119+
// slither-disable-next-line timestamp
117120
require(block.timestamp >= deadline, "Campaign ongoing");
118121
require(totalPledged < goal, "Goal met");
119122

src/defi/DiscreteStakingRewards.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,11 +135,11 @@ contract DiscreteStakingRewards {
135135
if (amount == 0) revert ZeroAmount();
136136
if (totalStaked == 0) revert InsufficientBalance(); // No stakers to receive rewards
137137

138-
if (!IERC20Staking(rewardToken).transferFrom(msg.sender, address(this), amount)) revert TransferFailed();
139-
140138
// Increase the global reward index: each staked token earns (amount / totalStaked)
141139
rewardIndex += (amount * 1e18) / totalStaked;
142140

141+
if (!IERC20Staking(rewardToken).transferFrom(msg.sender, address(this), amount)) revert TransferFailed();
142+
143143
emit RewardNotified(amount, rewardIndex);
144144
}
145145

src/defi/DutchAuction.sol

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,12 @@ contract DutchAuction {
8181

8282
/// @notice Get current price based on time elapsed
8383
function currentPrice() public view returns (uint256) {
84+
// slither-disable-next-line timestamp
8485
if (block.timestamp >= endsAt) {
8586
return minimumPrice;
8687
}
8788

89+
// slither-disable-next-line timestamp
8890
uint256 timePassed = block.timestamp - startsAt;
8991
uint256 maxDiscount = startingPrice - minimumPrice;
9092
// Avoid overflow: timePassed * discountRate
@@ -102,6 +104,7 @@ contract DutchAuction {
102104
/// @notice Purchase the NFT at current price
103105
function purchase() external payable {
104106
require(!ended, "Auction ended");
107+
// slither-disable-next-line timestamp
105108
require(block.timestamp < endsAt, "Auction expired");
106109

107110
uint256 price = currentPrice();
@@ -110,15 +113,15 @@ contract DutchAuction {
110113
// Mark as ended
111114
ended = true;
112115
buyer = msg.sender;
113-
114-
// Transfer NFT to buyer
115-
nft.safeTransferFrom(address(this), msg.sender, tokenId);
116116

117117
// Pull-payment accounting (avoids direct ETH sends during purchase)
118118
pendingWithdrawals[seller] += price;
119119
if (msg.value > price) {
120120
pendingWithdrawals[msg.sender] += (msg.value - price);
121121
}
122+
123+
// Transfer NFT to buyer
124+
nft.safeTransferFrom(address(this), msg.sender, tokenId);
122125

123126
emit AuctionEnded(msg.sender, price, block.timestamp);
124127
}

src/defi/EnglishAuction.sol

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ contract EnglishAuction {
7979
require(!started, "Already started");
8080

8181
started = true;
82+
// slither-disable-next-line timestamp
8283
endAt = block.timestamp + duration;
8384

8485
emit Started(block.timestamp, endAt);
@@ -87,6 +88,7 @@ contract EnglishAuction {
8788
/// @notice Place a bid
8889
function bid() external payable {
8990
require(started, "Not started");
91+
// slither-disable-next-line timestamp
9092
require(block.timestamp < endAt, "Ended");
9193
uint256 minRequired = highestBidder == address(0)
9294
? highestBid
@@ -120,16 +122,17 @@ contract EnglishAuction {
120122
function end() external {
121123
require(started, "Not started");
122124
require(!ended, "Already ended");
125+
// slither-disable-next-line timestamp
123126
require(block.timestamp >= endAt, "Not yet ended");
124127

125128
ended = true;
126129

127130
if (highestBidder != address(0)) {
128-
// Transfer NFT to winner
129-
nft.safeTransferFrom(address(this), highestBidder, tokenId);
130-
131131
// Pull-payment for seller proceeds (avoids direct ETH send in end())
132132
pendingWithdrawals[seller] += highestBid;
133+
134+
// Transfer NFT to winner
135+
nft.safeTransferFrom(address(this), highestBidder, tokenId);
133136
} else {
134137
// No bids - return NFT to seller
135138
nft.safeTransferFrom(address(this), seller, tokenId);

src/defi/StakingRewards.sol

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ contract StakingRewards {
9191
/// @notice Emitted when rewards are paid out
9292
event RewardPaid(address indexed user, uint256 reward);
9393

94+
/// @notice Emitted when rewards are notified and the distribution parameters are updated
95+
event RewardNotified(uint256 reward, uint256 rewardRate, uint256 periodFinish);
96+
9497
uint256 private _unlocked = 1;
9598
modifier nonReentrant() {
9699
require(_unlocked == 1, "Reentrancy");
@@ -126,6 +129,7 @@ contract StakingRewards {
126129

127130
/// @notice Last time reward was applicable
128131
function lastTimeRewardApplicable() public view returns (uint256) {
132+
// slither-disable-next-line timestamp
129133
return block.timestamp < periodFinish ? block.timestamp : periodFinish;
130134
}
131135

@@ -225,9 +229,11 @@ contract StakingRewards {
225229

226230
_updateReward(address(0));
227231

232+
// slither-disable-next-line timestamp
228233
if (block.timestamp >= periodFinish) {
229234
rewardRate = reward / rewardsDuration;
230235
} else {
236+
// slither-disable-next-line timestamp
231237
uint256 remainingRewards = (periodFinish - block.timestamp) * rewardRate;
232238
rewardRate = (reward + remainingRewards) / rewardsDuration;
233239
}
@@ -236,8 +242,12 @@ contract StakingRewards {
236242
uint256 balance = rewardsToken.balanceOf(address(this));
237243
require(rewardRate <= balance / rewardsDuration, "Reward amount > balance");
238244

245+
// slither-disable-next-line timestamp
239246
periodFinish = block.timestamp + rewardsDuration;
247+
// slither-disable-next-line timestamp
240248
lastUpdateTime = block.timestamp;
249+
250+
emit RewardNotified(reward, rewardRate, periodFinish);
241251
}
242252

243253
/// @notice Exit the staking (withdraw + claim)

0 commit comments

Comments
 (0)