Skip to content

Commit 2157931

Browse files
authored
Merge pull request #18 from KaelSensei/fix/slither-alerts-clean
Fix/slither alerts clean
2 parents 6d2ec7a + 7e7fdad commit 2157931

11 files changed

Lines changed: 59 additions & 41 deletions

Dockerfile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ ENV DEBIAN_FRONTEND=noninteractive
33

44
RUN apt-get update && apt-get install -y \
55
curl git build-essential pkg-config libssl-dev \
6+
python3 python3-pip \
67
&& curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
78
&& apt-get install -y nodejs \
89
&& rm -rf /var/lib/apt/lists/*
@@ -11,4 +12,9 @@ RUN curl -L https://foundry.paradigm.xyz | bash
1112
ENV PATH="/root/.foundry/bin:$PATH"
1213
RUN foundryup
1314

15+
# Slither (static analyzer) expects `solc` available on PATH
16+
RUN pip3 install --no-cache-dir slither-analyzer solc-select \
17+
&& solc-select install 0.8.26 \
18+
&& solc-select use 0.8.26
19+
1420
WORKDIR /workspace

PROGRESS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ A hands-on Solidity training ground based on solidity-by-example.org.
4545
- [x] Fuzz tests: 256 runs per test
4646
- [x] Invariant tests: 64 runs, 2048 calls
4747
- [x] All contracts have NatSpec documentation
48+
- [x] Slither findings: enforce ERC20 `transfer` / `transferFrom` return checks in AMM examples
49+
- [x] Slither findings: enforce ERC20 `transfer` / `transferFrom` return checks in DeFi examples (vault, lockers, staking, crowdfunding, swaps)
4850

4951
### ✅ Phase 5: Remaining Basic Topics (29/29 COMPLETE)
5052
- [x] DataLocations - storage, memory, calldata

src/defi/ConstantProductAMM.sol

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ contract ConstantProductAMM {
3737
error InsufficientLiquidity();
3838
error InsufficientShares();
3939
error InvalidRatio();
40+
error TransferFailed();
4041

4142
constructor(address _token0, address _token1) {
4243
token0 = IERC20AMM(_token0);
@@ -56,7 +57,7 @@ contract ConstantProductAMM {
5657
? (token0, token1, reserve0, reserve1)
5758
: (token1, token0, reserve1, reserve0);
5859

59-
_tokenIn.transferFrom(msg.sender, address(this), amountIn);
60+
if (!_tokenIn.transferFrom(msg.sender, address(this), amountIn)) revert TransferFailed();
6061

6162
// 0.3% fee: amountInWithFee = amountIn * 997 / 1000
6263
uint256 amountInWithFee = (amountIn * 997) / 1000;
@@ -66,7 +67,7 @@ contract ConstantProductAMM {
6667

6768
if (amountOut == 0) revert InsufficientLiquidity();
6869

69-
_tokenOut.transfer(msg.sender, amountOut);
70+
if (!_tokenOut.transfer(msg.sender, amountOut)) revert TransferFailed();
7071

7172
_updateReserves();
7273
emit Swap(msg.sender, tokenIn, amountIn, amountOut);
@@ -84,8 +85,8 @@ contract ConstantProductAMM {
8485
if (amount0 * reserve1 != amount1 * reserve0) revert InvalidRatio();
8586
}
8687

87-
token0.transferFrom(msg.sender, address(this), amount0);
88-
token1.transferFrom(msg.sender, address(this), amount1);
88+
if (!token0.transferFrom(msg.sender, address(this), amount0)) revert TransferFailed();
89+
if (!token1.transferFrom(msg.sender, address(this), amount1)) revert TransferFailed();
8990

9091
if (totalSupply == 0) {
9192
shares = _sqrt(amount0 * amount1);
@@ -121,8 +122,8 @@ contract ConstantProductAMM {
121122
balanceOf[msg.sender] -= shares;
122123
totalSupply -= shares;
123124

124-
token0.transfer(msg.sender, amount0);
125-
token1.transfer(msg.sender, amount1);
125+
if (!token0.transfer(msg.sender, amount0)) revert TransferFailed();
126+
if (!token1.transfer(msg.sender, amount1)) revert TransferFailed();
126127

127128
_updateReserves();
128129
emit RemoveLiquidity(msg.sender, shares, amount0, amount1);

src/defi/ConstantSumAMM.sol

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ contract ConstantSumAMM {
4040
error ZeroAmount();
4141
error InsufficientLiquidity();
4242
error InsufficientShares();
43+
error TransferFailed();
4344

4445
/// @param _token0 Address of the first token
4546
/// @param _token1 Address of the second token
@@ -64,7 +65,7 @@ contract ConstantSumAMM {
6465
? (token0, token1, reserve1)
6566
: (token1, token0, reserve0);
6667

67-
_tokenIn.transferFrom(msg.sender, address(this), amountIn);
68+
if (!_tokenIn.transferFrom(msg.sender, address(this), amountIn)) revert TransferFailed();
6869

6970
// 0.3% fee: amountOut = amountIn * 997 / 1000
7071
// Constant sum means 1:1 exchange rate — no price impact
@@ -73,7 +74,7 @@ contract ConstantSumAMM {
7374
if (amountOut > _resOut) revert InsufficientLiquidity();
7475
if (amountOut == 0) revert ZeroAmount();
7576

76-
_tokenOut.transfer(msg.sender, amountOut);
77+
if (!_tokenOut.transfer(msg.sender, amountOut)) revert TransferFailed();
7778

7879
_updateReserves();
7980
emit Swap(msg.sender, tokenIn, amountIn, amountOut);
@@ -89,10 +90,10 @@ contract ConstantSumAMM {
8990
if (amount0 == 0 && amount1 == 0) revert ZeroAmount();
9091

9192
if (amount0 > 0) {
92-
token0.transferFrom(msg.sender, address(this), amount0);
93+
if (!token0.transferFrom(msg.sender, address(this), amount0)) revert TransferFailed();
9394
}
9495
if (amount1 > 0) {
95-
token1.transferFrom(msg.sender, address(this), amount1);
96+
if (!token1.transferFrom(msg.sender, address(this), amount1)) revert TransferFailed();
9697
}
9798

9899
if (totalSupply == 0) {
@@ -129,10 +130,10 @@ contract ConstantSumAMM {
129130
totalSupply -= shares;
130131

131132
if (amount0 > 0) {
132-
token0.transfer(msg.sender, amount0);
133+
if (!token0.transfer(msg.sender, amount0)) revert TransferFailed();
133134
}
134135
if (amount1 > 0) {
135-
token1.transfer(msg.sender, amount1);
136+
if (!token1.transfer(msg.sender, amount1)) revert TransferFailed();
136137
}
137138

138139
_updateReserves();

src/defi/CrowdFund.sol

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ contract CrowdFund {
4848
/// @notice Emitted when goal not met and pledges are refunded
4949
event Refunded(address pledger, uint256 amount);
5050

51+
error TransferFailed();
52+
5153
/// @param _token Token address for contributions
5254
/// @param _creator Campaign creator
5355
/// @param _goal Funding goal
@@ -74,7 +76,7 @@ contract CrowdFund {
7476
require(amount > 0, "Cannot pledge 0");
7577

7678
// Transfer tokens from pledger
77-
token.transferFrom(msg.sender, address(this), amount);
79+
if (!token.transferFrom(msg.sender, address(this), amount)) revert TransferFailed();
7880

7981
// Update state
8082
pledges[msg.sender] += amount;
@@ -93,7 +95,7 @@ contract CrowdFund {
9395
totalPledged -= amount;
9496

9597
// Transfer tokens back to pledger
96-
token.transfer(msg.sender, amount);
98+
if (!token.transfer(msg.sender, amount)) revert TransferFailed();
9799

98100
emit Unpledged(msg.sender, amount);
99101
}
@@ -108,7 +110,7 @@ contract CrowdFund {
108110
claimed = true;
109111

110112
// Transfer all pledged tokens to creator
111-
token.transfer(creator, totalPledged);
113+
if (!token.transfer(creator, totalPledged)) revert TransferFailed();
112114

113115
emit Claimed(creator, totalPledged);
114116
}
@@ -126,7 +128,7 @@ contract CrowdFund {
126128
totalPledged -= amount;
127129

128130
// Transfer tokens back
129-
token.transfer(msg.sender, amount);
131+
if (!token.transfer(msg.sender, amount)) revert TransferFailed();
130132

131133
emit Refunded(msg.sender, amount);
132134
}

src/defi/DiscreteStakingRewards.sol

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ contract DiscreteStakingRewards {
5050
error InsufficientBalance();
5151
error NotOwner();
5252
error NoRewards();
53+
error TransferFailed();
5354

5455
/// @param _stakingToken Address of the token users stake
5556
/// @param _rewardToken Address of the token distributed as rewards
@@ -72,7 +73,7 @@ contract DiscreteStakingRewards {
7273
// Settle any pending rewards before changing the user's stake
7374
_updateRewards(msg.sender);
7475

75-
IERC20Staking(stakingToken).transferFrom(msg.sender, address(this), amount);
76+
if (!IERC20Staking(stakingToken).transferFrom(msg.sender, address(this), amount)) revert TransferFailed();
7677

7778
stakedBalance[msg.sender] += amount;
7879
totalStaked += amount;
@@ -93,7 +94,7 @@ contract DiscreteStakingRewards {
9394
stakedBalance[msg.sender] -= amount;
9495
totalStaked -= amount;
9596

96-
IERC20Staking(stakingToken).transfer(msg.sender, amount);
97+
if (!IERC20Staking(stakingToken).transfer(msg.sender, amount)) revert TransferFailed();
9798

9899
emit Withdrawn(msg.sender, amount);
99100
}
@@ -108,7 +109,7 @@ contract DiscreteStakingRewards {
108109

109110
userRewards[msg.sender] = 0;
110111

111-
IERC20Staking(rewardToken).transfer(msg.sender, amount);
112+
if (!IERC20Staking(rewardToken).transfer(msg.sender, amount)) revert TransferFailed();
112113

113114
emit RewardClaimed(msg.sender, amount);
114115
}
@@ -129,7 +130,7 @@ contract DiscreteStakingRewards {
129130
if (amount == 0) revert ZeroAmount();
130131
if (totalStaked == 0) revert InsufficientBalance(); // No stakers to receive rewards
131132

132-
IERC20Staking(rewardToken).transferFrom(msg.sender, address(this), amount);
133+
if (!IERC20Staking(rewardToken).transferFrom(msg.sender, address(this), amount)) revert TransferFailed();
133134

134135
// Increase the global reward index: each staked token earns (amount / totalStaked)
135136
rewardIndex += (amount * 1e18) / totalStaked;

src/defi/StableSwapAMM.sol

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ contract StableSwapAMM {
5252
error InsufficientLiquidity();
5353
error InsufficientShares();
5454
error ConvergenceFailed();
55+
error TransferFailed();
5556

5657
/// @param _token0 Address of the first token
5758
/// @param _token1 Address of the second token
@@ -77,7 +78,7 @@ contract StableSwapAMM {
7778
? (token0, token1, reserve0, reserve1)
7879
: (token1, token0, reserve1, reserve0);
7980

80-
_tokenIn.transferFrom(msg.sender, address(this), amountIn);
81+
if (!_tokenIn.transferFrom(msg.sender, address(this), amountIn)) revert TransferFailed();
8182

8283
// Apply 0.3% fee to the input amount
8384
uint256 amountInWithFee = (amountIn * 997) / 1000;
@@ -94,7 +95,7 @@ contract StableSwapAMM {
9495
amountOut = _resOut - newResOut;
9596
if (amountOut == 0) revert InsufficientLiquidity();
9697

97-
_tokenOut.transfer(msg.sender, amountOut);
98+
if (!_tokenOut.transfer(msg.sender, amountOut)) revert TransferFailed();
9899

99100
_updateReserves();
100101
emit Swap(msg.sender, tokenIn, amountIn, amountOut);
@@ -108,10 +109,10 @@ contract StableSwapAMM {
108109
if (amount0 == 0 && amount1 == 0) revert ZeroAmount();
109110

110111
if (amount0 > 0) {
111-
token0.transferFrom(msg.sender, address(this), amount0);
112+
if (!token0.transferFrom(msg.sender, address(this), amount0)) revert TransferFailed();
112113
}
113114
if (amount1 > 0) {
114-
token1.transferFrom(msg.sender, address(this), amount1);
115+
if (!token1.transferFrom(msg.sender, address(this), amount1)) revert TransferFailed();
115116
}
116117

117118
if (totalSupply == 0) {
@@ -150,10 +151,10 @@ contract StableSwapAMM {
150151
totalSupply -= shares;
151152

152153
if (amount0 > 0) {
153-
token0.transfer(msg.sender, amount0);
154+
if (!token0.transfer(msg.sender, amount0)) revert TransferFailed();
154155
}
155156
if (amount1 > 0) {
156-
token1.transfer(msg.sender, amount1);
157+
if (!token1.transfer(msg.sender, amount1)) revert TransferFailed();
157158
}
158159

159160
_updateReserves();

src/defi/StakingRewards.sol

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ library SafeERC20 {
3333
}
3434

3535
function _callOptionalReturn(IERC20Minimal token, bytes memory data) private {
36-
(bool success, bytes memory result) = address(token).call(data);
36+
(bool success, ) = address(token).call(data);
3737
require(success, "SafeERC20: call failed");
3838
}
3939
}
@@ -162,7 +162,7 @@ contract StakingRewards {
162162
}
163163

164164
/// @notice Stake with permit
165-
function stakeWithPermit(uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external {
165+
function stakeWithPermit(uint256 amount, uint256, uint8, bytes32, bytes32) external {
166166
// Note: In production, implement permit signature
167167
_stake(msg.sender, amount);
168168
}
@@ -175,7 +175,7 @@ contract StakingRewards {
175175
_updateReward(_user);
176176

177177
// Transfer tokens from user
178-
stakingToken.transferFrom(_user, address(this), amount);
178+
stakingToken.safeTransferFrom(_user, address(this), amount);
179179

180180
// Update state
181181
_balances[_user] += amount;
@@ -196,7 +196,7 @@ contract StakingRewards {
196196
_totalSupply -= amount;
197197

198198
// Transfer tokens to user
199-
stakingToken.transfer(msg.sender, amount);
199+
stakingToken.safeTransfer(msg.sender, amount);
200200

201201
emit Withdrawn(msg.sender, amount);
202202
}
@@ -208,7 +208,7 @@ contract StakingRewards {
208208
uint256 reward = rewards[msg.sender];
209209
if (reward > 0) {
210210
rewards[msg.sender] = 0;
211-
rewardsToken.transfer(msg.sender, reward);
211+
rewardsToken.safeTransfer(msg.sender, reward);
212212
emit RewardPaid(msg.sender, reward);
213213
}
214214
}
@@ -247,12 +247,12 @@ contract StakingRewards {
247247
_balances[msg.sender] = 0;
248248
_totalSupply = 0;
249249

250-
stakingToken.transfer(msg.sender, amount);
250+
stakingToken.safeTransfer(msg.sender, amount);
251251

252252
uint256 reward = rewards[msg.sender];
253253
if (reward > 0) {
254254
rewards[msg.sender] = 0;
255-
rewardsToken.transfer(msg.sender, reward);
255+
rewardsToken.safeTransfer(msg.sender, reward);
256256
emit RewardPaid(msg.sender, reward);
257257
}
258258

src/defi/TokenLocker.sol

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ contract TokenLocker {
4141
error NotBeneficiary();
4242
error NotYetUnlocked();
4343
error AlreadyWithdrawn();
44+
error TransferFailed();
4445

4546
/// @notice Create a new token lock
4647
/// @dev Transfers tokens from the caller into this contract and records the lock.
@@ -61,7 +62,7 @@ contract TokenLocker {
6162
if (beneficiary == address(0)) revert ZeroAddress();
6263
if (unlockTime <= block.timestamp) revert UnlockTimeInPast();
6364

64-
IERC20Locker(token).transferFrom(msg.sender, address(this), amount);
65+
if (!IERC20Locker(token).transferFrom(msg.sender, address(this), amount)) revert TransferFailed();
6566

6667
lockId = locks.length;
6768
locks.push(Lock({
@@ -87,7 +88,7 @@ contract TokenLocker {
8788

8889
lock.withdrawn = true;
8990

90-
IERC20Locker(lock.token).transfer(msg.sender, lock.amount);
91+
if (!IERC20Locker(lock.token).transfer(msg.sender, lock.amount)) revert TransferFailed();
9192

9293
emit Withdrawn(lockId, msg.sender, lock.amount);
9394
}

src/defi/UniswapV3Swap.sol

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ contract UniswapV3Swap {
6464
/// @notice Emitted when swap completes
6565
event SwapCompleted(address tokenIn, address tokenOut, uint256 amountIn, uint256 amountOut);
6666

67+
error TransferFailed();
68+
6769
/// @param _router Address of Uniswap V3 Router
6870
constructor(address _router) {
6971
router = ISwapRouter(_router);
@@ -84,7 +86,7 @@ contract UniswapV3Swap {
8486
uint256 amountOutMinimum
8587
) external returns (uint256 amountOut) {
8688
// Transfer tokens from caller
87-
IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn);
89+
if (!IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn)) revert TransferFailed();
8890

8991
// Approve router
9092
IERC20(tokenIn).approve(address(router), amountIn);
@@ -121,7 +123,7 @@ contract UniswapV3Swap {
121123
uint256 amountInMaximum
122124
) external returns (uint256 amountIn) {
123125
// Transfer max tokens from caller
124-
IERC20(tokenIn).transferFrom(msg.sender, address(this), amountInMaximum);
126+
if (!IERC20(tokenIn).transferFrom(msg.sender, address(this), amountInMaximum)) revert TransferFailed();
125127

126128
// Approve router
127129
IERC20(tokenIn).approve(address(router), amountInMaximum);
@@ -142,7 +144,7 @@ contract UniswapV3Swap {
142144

143145
// Refund unused tokens
144146
if (amountInMaximum > amountIn) {
145-
IERC20(tokenIn).transfer(msg.sender, amountInMaximum - amountIn);
147+
if (!IERC20(tokenIn).transfer(msg.sender, amountInMaximum - amountIn)) revert TransferFailed();
146148
}
147149

148150
emit SwapCompleted(tokenIn, tokenOut, amountIn, amountOut);
@@ -160,7 +162,7 @@ contract UniswapV3Swap {
160162
) external returns (uint256 amountOut) {
161163
// Transfer tokens from caller
162164
address tokenIn = extractTokenInFromPath(path);
163-
IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn);
165+
if (!IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn)) revert TransferFailed();
164166

165167
// Approve router
166168
IERC20(tokenIn).approve(address(router), amountIn);

0 commit comments

Comments
 (0)