Skip to content

Commit ab3ceaa

Browse files
committed
fix : reentrancy-events issue
1 parent b7e4b93 commit ab3ceaa

2 files changed

Lines changed: 134 additions & 81 deletions

File tree

src/CrowdFund.sol

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,11 @@ contract CrowdFund {
4040
// ========== 事件 ==========
4141

4242
// 当有人贡献资金时触发
43-
event ContributionReceived(address indexed contributor, uint256 amount, uint256 totalFunded);
43+
event ContributionReceived(
44+
address indexed contributor,
45+
uint256 amount,
46+
uint256 totalFunded
47+
);
4448

4549
// 当创建者提取资金时触发
4650
event FundsWithdrawn(address indexed owner, uint256 amount);
@@ -136,17 +140,22 @@ contract CrowdFund {
136140
* @dev 创建者提取资金(仅在成功时)
137141
* @notice 只有合约创建者可以调用
138142
*/
139-
function withdrawFunds() public onlyOwner afterDeadline inState(State.Successful) {
143+
function withdrawFunds()
144+
public
145+
onlyOwner
146+
afterDeadline
147+
inState(State.Successful)
148+
{
140149
require(!fundsWithdrawn, "Funds already withdrawn");
141150

142151
fundsWithdrawn = true;
143152
uint256 amount = address(this).balance;
144153

154+
emit FundsWithdrawn(owner, amount);
155+
145156
// 使用 call 转账(推荐的安全方式)
146-
(bool success,) = payable(owner).call{value: amount}("");
157+
(bool success, ) = payable(owner).call{value: amount}("");
147158
require(success, "Transfer failed");
148-
149-
emit FundsWithdrawn(owner, amount);
150159
}
151160

152161
/**
@@ -160,11 +169,13 @@ contract CrowdFund {
160169
// 先更新状态,防止重入攻击(Checks-Effects-Interactions 模式)
161170
contributions[msg.sender] = 0;
162171

172+
emit RefundIssued(msg.sender, contributedAmount);
173+
163174
// 转账退款
164-
(bool success,) = payable(msg.sender).call{value: contributedAmount}("");
175+
(bool success, ) = payable(msg.sender).call{value: contributedAmount}(
176+
""
177+
);
165178
require(success, "Refund transfer failed");
166-
167-
emit RefundIssued(msg.sender, contributedAmount);
168179
}
169180

170181
// ========== 查询函数 ==========
@@ -197,7 +208,9 @@ contract CrowdFund {
197208
/**
198209
* @dev 检查某个地址的贡献金额
199210
*/
200-
function getContribution(address _contributor) public view returns (uint256) {
211+
function getContribution(
212+
address _contributor
213+
) public view returns (uint256) {
201214
return contributions[_contributor];
202215
}
203216

src/GameCharacter.sol

Lines changed: 112 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,18 @@ contract GameCharacter is ERC721, Ownable {
3636
// ============================================
3737

3838
event CharacterMinted(
39-
address indexed owner, uint256 indexed tokenId, uint256 level, uint256 strength, uint256 health
39+
address indexed owner,
40+
uint256 indexed tokenId,
41+
uint256 level,
42+
uint256 strength,
43+
uint256 health
4044
);
4145
event CharacterLeveledUp(uint256 indexed tokenId, uint256 newLevel);
42-
event ExperienceGained(uint256 indexed tokenId, uint256 xpGained, uint256 totalXP);
46+
event ExperienceGained(
47+
uint256 indexed tokenId,
48+
uint256 xpGained,
49+
uint256 totalXP
50+
);
4351

4452
// ============================================
4553
// CONSTRUCTOR
@@ -60,18 +68,21 @@ contract GameCharacter is ERC721, Ownable {
6068
_nextTokenId++;
6169

6270
tokenAttributes[tokenId] = CharacterAttributes({
63-
level: 1, strength: _pseudoRandom(10, 20, tokenId), health: _pseudoRandom(50, 100, tokenId), experience: 0
71+
level: 1,
72+
strength: _pseudoRandom(10, 20, tokenId),
73+
health: _pseudoRandom(50, 100, tokenId),
74+
experience: 0
6475
});
6576

66-
_safeMint(to, tokenId);
67-
6877
emit CharacterMinted(
6978
to,
7079
tokenId,
7180
tokenAttributes[tokenId].level,
7281
tokenAttributes[tokenId].strength,
7382
tokenAttributes[tokenId].health
7483
);
84+
85+
_safeMint(to, tokenId);
7586
}
7687

7788
// ============================================
@@ -132,45 +143,48 @@ contract GameCharacter is ERC721, Ownable {
132143
* @param tokenId The token to generate an image for
133144
* @return SVG image as a string
134145
*/
135-
function generateCharacterImage(uint256 tokenId) public view returns (string memory) {
146+
function generateCharacterImage(
147+
uint256 tokenId
148+
) public view returns (string memory) {
136149
CharacterAttributes memory attrs = tokenAttributes[tokenId];
137150

138151
// Choose color based on level
139152
string memory color = _getColorForLevel(attrs.level);
140153

141154
// Build the SVG
142-
return string(
143-
abi.encodePacked(
144-
'<svg xmlns="http://www.w3.org/2000/svg" width="350" height="350" style="background:#1a1a2e">',
145-
// Character body (a simple rectangle that changes color by level)
146-
'<rect x="125" y="120" width="100" height="150" fill="',
147-
color,
148-
'" stroke="#fff" stroke-width="3" rx="10"/>',
149-
// Head
150-
'<circle cx="175" cy="80" r="35" fill="',
151-
color,
152-
'" stroke="#fff" stroke-width="3"/>',
153-
// Eyes
154-
'<circle cx="165" cy="75" r="5" fill="#fff"/>',
155-
'<circle cx="185" cy="75" r="5" fill="#fff"/>',
156-
// Level badge
157-
'<rect x="15" y="15" width="80" height="40" fill="#e94560" rx="8"/>',
158-
'<text x="55" y="42" font-family="Arial" font-size="20" fill="#fff" text-anchor="middle" font-weight="bold">LVL ',
159-
attrs.level.toString(),
160-
"</text>",
161-
// Stats display
162-
'<text x="175" y="300" font-family="Arial" font-size="14" fill="#fff" text-anchor="middle">STR: ',
163-
attrs.strength.toString(),
164-
"</text>",
165-
'<text x="175" y="320" font-family="Arial" font-size="14" fill="#fff" text-anchor="middle">HP: ',
166-
attrs.health.toString(),
167-
"</text>",
168-
'<text x="175" y="340" font-family="Arial" font-size="14" fill="#fff" text-anchor="middle">XP: ',
169-
attrs.experience.toString(),
170-
"</text>",
171-
"</svg>"
172-
)
173-
);
155+
return
156+
string(
157+
abi.encodePacked(
158+
'<svg xmlns="http://www.w3.org/2000/svg" width="350" height="350" style="background:#1a1a2e">',
159+
// Character body (a simple rectangle that changes color by level)
160+
'<rect x="125" y="120" width="100" height="150" fill="',
161+
color,
162+
'" stroke="#fff" stroke-width="3" rx="10"/>',
163+
// Head
164+
'<circle cx="175" cy="80" r="35" fill="',
165+
color,
166+
'" stroke="#fff" stroke-width="3"/>',
167+
// Eyes
168+
'<circle cx="165" cy="75" r="5" fill="#fff"/>',
169+
'<circle cx="185" cy="75" r="5" fill="#fff"/>',
170+
// Level badge
171+
'<rect x="15" y="15" width="80" height="40" fill="#e94560" rx="8"/>',
172+
'<text x="55" y="42" font-family="Arial" font-size="20" fill="#fff" text-anchor="middle" font-weight="bold">LVL ',
173+
attrs.level.toString(),
174+
"</text>",
175+
// Stats display
176+
'<text x="175" y="300" font-family="Arial" font-size="14" fill="#fff" text-anchor="middle">STR: ',
177+
attrs.strength.toString(),
178+
"</text>",
179+
'<text x="175" y="320" font-family="Arial" font-size="14" fill="#fff" text-anchor="middle">HP: ',
180+
attrs.health.toString(),
181+
"</text>",
182+
'<text x="175" y="340" font-family="Arial" font-size="14" fill="#fff" text-anchor="middle">XP: ',
183+
attrs.experience.toString(),
184+
"</text>",
185+
"</svg>"
186+
)
187+
);
174188
}
175189

176190
/**
@@ -180,43 +194,51 @@ contract GameCharacter is ERC721, Ownable {
180194
* @param tokenId The token to generate metadata for
181195
* @return JSON metadata as a string
182196
*/
183-
function generateMetadata(uint256 tokenId) public view returns (string memory) {
197+
function generateMetadata(
198+
uint256 tokenId
199+
) public view returns (string memory) {
184200
CharacterAttributes memory attrs = tokenAttributes[tokenId];
185201

186202
// Generate the SVG image
187203
string memory svg = generateCharacterImage(tokenId);
188204

189205
// Encode SVG to Base64
190-
string memory imageURI = string(abi.encodePacked("data:image/svg+xml;base64,", Base64.encode(bytes(svg))));
191-
192-
// Build JSON metadata
193-
return string(
206+
string memory imageURI = string(
194207
abi.encodePacked(
195-
"{",
196-
'"name": "GameCharacter #',
197-
tokenId.toString(),
198-
'",',
199-
'"description": "A dynamic game character that grows stronger with training!",',
200-
'"image": "',
201-
imageURI,
202-
'",',
203-
'"attributes": [',
204-
'{"trait_type": "Level", "value": ',
205-
attrs.level.toString(),
206-
"},",
207-
'{"trait_type": "Strength", "value": ',
208-
attrs.strength.toString(),
209-
"},",
210-
'{"trait_type": "Health", "value": ',
211-
attrs.health.toString(),
212-
"},",
213-
'{"trait_type": "Experience", "value": ',
214-
attrs.experience.toString(),
215-
"}",
216-
"]",
217-
"}"
208+
"data:image/svg+xml;base64,",
209+
Base64.encode(bytes(svg))
218210
)
219211
);
212+
213+
// Build JSON metadata
214+
return
215+
string(
216+
abi.encodePacked(
217+
"{",
218+
'"name": "GameCharacter #',
219+
tokenId.toString(),
220+
'",',
221+
'"description": "A dynamic game character that grows stronger with training!",',
222+
'"image": "',
223+
imageURI,
224+
'",',
225+
'"attributes": [',
226+
'{"trait_type": "Level", "value": ',
227+
attrs.level.toString(),
228+
"},",
229+
'{"trait_type": "Strength", "value": ',
230+
attrs.strength.toString(),
231+
"},",
232+
'{"trait_type": "Health", "value": ',
233+
attrs.health.toString(),
234+
"},",
235+
'{"trait_type": "Experience", "value": ',
236+
attrs.experience.toString(),
237+
"}",
238+
"]",
239+
"}"
240+
)
241+
);
220242
}
221243

222244
/**
@@ -226,12 +248,20 @@ contract GameCharacter is ERC721, Ownable {
226248
* @param tokenId The token to get the URI for
227249
* @return The full token URI (a data URI with base64-encoded JSON)
228250
*/
229-
function tokenURI(uint256 tokenId) public view override returns (string memory) {
251+
function tokenURI(
252+
uint256 tokenId
253+
) public view override returns (string memory) {
230254
require(_ownerOf(tokenId) != address(0), "Token does not exist");
231255

232256
string memory json = generateMetadata(tokenId);
233257

234-
return string(abi.encodePacked("data:application/json;base64,", Base64.encode(bytes(json))));
258+
return
259+
string(
260+
abi.encodePacked(
261+
"data:application/json;base64,",
262+
Base64.encode(bytes(json))
263+
)
264+
);
235265
}
236266

237267
// ============================================
@@ -242,15 +272,19 @@ contract GameCharacter is ERC721, Ownable {
242272
* @dev Get color based on character level
243273
* Higher level = cooler colors!
244274
*/
245-
function _getColorForLevel(uint256 level) internal pure returns (string memory) {
275+
function _getColorForLevel(
276+
uint256 level
277+
) internal pure returns (string memory) {
246278
if (level >= 10) return "#ffd700"; // Gold
247279
if (level >= 7) return "#9b59b6"; // Purple
248280
if (level >= 5) return "#3498db"; // Blue
249281
if (level >= 3) return "#2ecc71"; // Green
250282
return "#95a5a6"; // Gray (level 1-2)
251283
}
252284

253-
function getCharacterAttributes(uint256 tokenId) public view returns (CharacterAttributes memory) {
285+
function getCharacterAttributes(
286+
uint256 tokenId
287+
) public view returns (CharacterAttributes memory) {
254288
require(_ownerOf(tokenId) != address(0), "Token does not exist");
255289
return tokenAttributes[tokenId];
256290
}
@@ -260,10 +294,16 @@ contract GameCharacter is ERC721, Ownable {
260294
}
261295

262296
// slither-disable-start weak-prng
263-
function _pseudoRandom(uint256 min, uint256 max, uint256 seed) private view returns (uint256) {
297+
function _pseudoRandom(
298+
uint256 min,
299+
uint256 max,
300+
uint256 seed
301+
) private view returns (uint256) {
264302
// We use block.prevrandao (The Beacon Chain randomness)
265303
// We removed block.timestamp to reduce miner influence vectors
266-
uint256 randomHash = uint256(keccak256(abi.encodePacked(block.prevrandao, msg.sender, seed)));
304+
uint256 randomHash = uint256(
305+
keccak256(abi.encodePacked(block.prevrandao, msg.sender, seed))
306+
);
267307

268308
return min + (randomHash % (max - min + 1));
269309
}

0 commit comments

Comments
 (0)