@@ -3,11 +3,12 @@ pragma solidity ^0.8.20;
33
44import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol " ;
55import "@openzeppelin/contracts/access/Ownable.sol " ;
6+ import "@openzeppelin/contracts/utils/ReentrancyGuard.sol " ;
67import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol " ;
78import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol " ;
89import "@openzeppelin/contracts/utils/Strings.sol " ;
910
10- contract VouchMe is ERC721URIStorage , Ownable {
11+ contract VouchMe is ERC721URIStorage , Ownable , ReentrancyGuard {
1112 using ECDSA for bytes32 ;
1213 using Strings for uint256 ;
1314
@@ -85,7 +86,7 @@ contract VouchMe is ERC721URIStorage, Ownable {
8586 string calldata giverName ,
8687 string calldata profileUrl ,
8788 bytes calldata signature
88- ) external payable returns (uint256 ) {
89+ ) external payable nonReentrant returns (uint256 ) {
8990 // Hash the message that was signed
9091 bytes32 messageHash = keccak256 (
9192 abi.encodePacked (
@@ -112,28 +113,13 @@ contract VouchMe is ERC721URIStorage, Ownable {
112113 _removeTestimonialFromList (existingTokenId, senderAddress, msg .sender );
113114 }
114115
115- // Fee logic: only charge if monetization is enabled and user exceeds free threshold
116- // This check happens BEFORE minting to ensure payment is received
116+ // Determine fee requirement before modifying testimonial state
117117 uint256 currentCount = _receivedTestimonials[msg .sender ].length ;
118118 uint256 requiredFee = _calculateRequiredFee (currentCount);
119119 if (requiredFee > 0 ) {
120120 require (msg .value >= requiredFee, "Insufficient fee payment " );
121-
122- // Transfer fee to treasury
123- (bool sent , ) = treasury.call {value: requiredFee}("" );
124- require (sent, "Fee transfer failed " );
125- emit FeePaid (msg .sender , requiredFee);
126-
127- // Refund excess payment
128- uint256 excess = msg .value - requiredFee;
129- if (excess > 0 ) {
130- (bool refunded , ) = msg .sender .call {value: excess}("" );
131- require (refunded, "Refund failed " );
132- }
133121 } else if (msg .value > 0 ) {
134- // Refund any payment when no fee is required
135- (bool refunded , ) = msg .sender .call {value: msg .value }("" );
136- require (refunded, "Refund failed " );
122+ // No fee is required, so any payment should be refunded later
137123 }
138124
139125 uint256 newTokenId = ++ _tokenIdTracker; // Manually increment token ID
@@ -175,6 +161,22 @@ contract VouchMe is ERC721URIStorage, Ownable {
175161 if (existingTokenId != 0 ) {
176162 emit TestimonialUpdated (senderAddress, msg .sender , newTokenId);
177163 }
164+
165+ // Interactions: transfer fee/refund only after all state changes and events above.
166+ if (requiredFee > 0 ) {
167+ (bool sent , ) = treasury.call {value: requiredFee}("" );
168+ require (sent, "Fee transfer failed " );
169+ emit FeePaid (msg .sender , requiredFee);
170+
171+ uint256 excess = msg .value - requiredFee;
172+ if (excess > 0 ) {
173+ (bool refunded , ) = msg .sender .call {value: excess}("" );
174+ require (refunded, "Refund failed " );
175+ }
176+ } else if (msg .value > 0 ) {
177+ (bool refunded , ) = msg .sender .call {value: msg .value }("" );
178+ require (refunded, "Refund failed " );
179+ }
178180
179181 return newTokenId;
180182 }
@@ -419,6 +421,23 @@ contract VouchMe is ERC721URIStorage, Ownable {
419421 uint256 currentCount = _receivedTestimonials[user].length ;
420422 return _calculateRequiredFee (currentCount);
421423 }
424+
425+ /**
426+ * @dev Returns the fee required for createTestimonial, accounting for replacement.
427+ * @param sender The testimonial sender address
428+ * @param receiver The testimonial receiver address (msg.sender in createTestimonial)
429+ * @return requiredFee The fee amount in wei after replacement adjustment
430+ */
431+ function getRequiredFeeForCreate (address sender , address receiver ) external view returns (uint256 requiredFee ) {
432+ uint256 currentCount = _receivedTestimonials[receiver].length ;
433+
434+ // createTestimonial removes an existing sender->receiver testimonial before fee calculation
435+ if (_testimonial[sender][receiver] != 0 && currentCount > 0 ) {
436+ currentCount -= 1 ;
437+ }
438+
439+ return _calculateRequiredFee (currentCount);
440+ }
422441
423442 /**
424443 * @dev Internal function to calculate the required fee based on current testimonial count
0 commit comments