@@ -6,9 +6,10 @@ import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
66import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol " ;
77import "@openzeppelin/contracts/utils/Context.sol " ;
88import "@openzeppelin/contracts/token/ERC20/IERC20.sol " ;
9+ import "@openzeppelin/contracts/security/ReentrancyGuard.sol " ;
910
1011
11- contract MVPCLR is OwnableUpgradeable {
12+ contract MVPCLR is OwnableUpgradeable , ReentrancyGuard {
1213
1314 event AdminAdded (address _admin );
1415 event AdminRemoved (address _admin );
@@ -33,6 +34,18 @@ contract MVPCLR is OwnableUpgradeable {
3334 uint256 roundId
3435 );
3536
37+ event DonateExternal (
38+ address sender ,
39+ uint256 value ,
40+ address patronAddress ,
41+ uint256 roundId ,
42+ uint16 externalType ,
43+ address target ,
44+ bytes callData ,
45+ address token ,
46+ uint256 gained
47+ );
48+
3649 event FailedDistribute (
3750 address receiver ,
3851 uint256 amount
@@ -130,24 +143,114 @@ contract MVPCLR is OwnableUpgradeable {
130143 emit PatronsAdded (addresses);
131144 }
132145
133- function donate (address [] memory patronAddresses , uint256 [] memory amounts ) public payable {
146+ function donate (
147+ address [] memory patronAddresses ,
148+ uint256 [] memory amounts ,
149+ bytes [] calldata metadata ,
150+ address [] calldata tokens
151+ ) public payable nonReentrant {
134152 require (patronAddresses.length == amounts.length , "CLR:donate - Mismatch between number of patrons and amounts " );
153+ require (patronAddresses.length == metadata.length , "CLR:donate - mismatch patrons/metadata " );
154+ require (! isBlacklisted[_msgSender ()], "Sender address is blacklisted " );
155+
135156 uint256 totalAmount = 0 ;
136157 uint256 donationRoundId = roundIsClosed () ? 0 : roundId;
158+
137159 for (uint256 i = 0 ; i < patronAddresses.length ; i++ ) {
138- address patronAddress = patronAddresses[i];
139- uint256 amount = amounts[i];
140- totalAmount += amount;
141- require (! isBlacklisted[_msgSender ()], "Sender address is blacklisted " );
142- require (isPatron[patronAddress], "CLR:donate - Not a valid recipient " );
143- emit Donate (_msgSender (), amount, patronAddress, donationRoundId);
144- bool success = payable (patronAddress).send (amount);
145- require (success, "CLR:donate - Failed to send funds to recipient " );
160+ totalAmount += _processDonation (
161+ patronAddresses[i],
162+ amounts[i],
163+ metadata[i],
164+ tokens[i],
165+ donationRoundId
166+ );
146167 }
147168
148- require (totalAmount <= msg .value , "CLR:donate - Total amount donated is greater than the value sent " );
169+ require (totalAmount <= msg .value , "CLR:donate - Total amount donated exceeds value sent " );
149170 }
150171
172+ function _processDonation (
173+ address patronAddress ,
174+ uint256 amount ,
175+ bytes calldata data ,
176+ address token ,
177+ uint256 donationRoundId
178+ ) internal returns (uint256 ) {
179+ require (! isBlacklisted[patronAddress], "Patron address is blacklisted " );
180+ require (isPatron[patronAddress], "CLR:donate - Not a valid recipient " );
181+
182+ if (data.length > 0 ) {
183+ _handleExternalDonation (patronAddress, amount, data, token, donationRoundId);
184+ } else {
185+ _handleDirectDonation (patronAddress, amount, donationRoundId);
186+ }
187+
188+ return amount;
189+ }
190+
191+ function _handleDirectDonation (
192+ address patronAddress ,
193+ uint256 amount ,
194+ uint256 donationRoundId
195+ ) internal {
196+ bool success = payable (patronAddress).send (amount);
197+ require (success, "CLR:donate - Failed to send funds to recipient " );
198+
199+ emit Donate (_msgSender (), amount, patronAddress, donationRoundId);
200+ }
201+
202+ function _handleExternalDonation (
203+ address patronAddress ,
204+ uint256 amount ,
205+ bytes calldata data ,
206+ address token ,
207+ uint256 donationRoundId
208+ ) internal {
209+ (uint16 externalType , address target , bytes memory callData ) = abi.decode (data, (uint16 , address , bytes ));
210+ require (target != address (0 ), "CLR:donate - invalid target " );
211+ require (isContract (target), "CLR:donate - target must be contract " );
212+
213+ uint256 gained = 0 ;
214+ uint256 beforeBal = 0 ;
215+
216+ if (token != address (0 )) {
217+ beforeBal = IERC20 (token).balanceOf (address (this ));
218+ }
219+ {
220+ (bool success , bytes memory returndata ) = payable (target).call {value: amount}(callData);
221+ require (success, _getRevertMsg (returndata));
222+ }
223+ if (token != address (0 )) {
224+ gained = IERC20 (token).balanceOf (address (this )) - beforeBal;
225+ if (gained > 0 ) {
226+ IERC20 (token).transfer (_msgSender (), gained);
227+ }
228+ }
229+
230+ emit DonateExternal (
231+ _msgSender (),
232+ amount,
233+ patronAddress,
234+ donationRoundId,
235+ externalType,
236+ target,
237+ callData,
238+ token,
239+ gained
240+ );
241+ }
242+
243+ function isContract (address account ) internal view returns (bool ) {
244+ return account.code.length > 0 ;
245+ }
246+
247+ function _getRevertMsg (bytes memory returnData ) private pure returns (string memory ) {
248+ if (returnData.length < 68 ) return "CLR:donate - external call failed " ;
249+ assembly {
250+ returnData := add (returnData, 0x04 )
251+ }
252+ return abi.decode (returnData, (string ));
253+ }
151254
152255 function distribute (address payable [] memory patrons , uint [] memory amounts , address token ) public onlyAdmin {
153256 require (patrons.length == amounts.length , "Length of patrons and amounts must be the same " );
0 commit comments