Skip to content

Commit 81fa94e

Browse files
Merge pull request #2 from Giveth/f_setup_tests_for_contract
test: add donation handler test cases
2 parents 7288833 + 9a17ec6 commit 81fa94e

6 files changed

+491
-8
lines changed

README.md

+100
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,103 @@
1+
# DonationHandler Smart Contract
2+
3+
A flexible and secure smart contract system for handling both ETH and ERC20 token donations with support for single and batch transactions.
4+
5+
## Overview
6+
7+
The DonationHandler is an upgradeable smart contract that facilitates donations in both ETH and ERC20 tokens. It supports single donations as well as batch donations to multiple recipients, with optional data attachments for each transaction.
8+
9+
### Key Features
10+
11+
- **Multiple Asset Support**: Handle both ETH and ERC20 token donations
12+
- **Batch Processing**: Efficiently process multiple donations in a single transaction
13+
- **Upgradeable**: Uses OpenZeppelin's upgradeable contract pattern
14+
- **Security Features**:
15+
- Reentrancy protection
16+
- Input validation
17+
- Ownership controls
18+
- Custom error handling
19+
20+
### Core Functionality
21+
22+
1. **ETH Donations**
23+
- Single ETH donations via `donateETH()`
24+
- Batch ETH donations via `donateManyETH()`
25+
- Direct ETH transfers are prevented
26+
27+
2. **ERC20 Donations**
28+
- Single token donations via `donateERC20()`
29+
- Batch token donations via `donateManyERC20()`
30+
- Automatic allowance checking
31+
32+
### Security Measures
33+
34+
- ReentrancyGuard implementation
35+
- Input validation for array lengths
36+
- Zero address checks
37+
- Amount validation
38+
- ERC20 allowance verification
39+
- Custom error handling for better gas efficiency
40+
41+
### Testing Coverage
42+
43+
The contract includes comprehensive test coverage for:
44+
- Single ETH donations
45+
- Multiple ETH donations
46+
- Single ERC20 token donations
47+
- Multiple ERC20 token donations
48+
- Error cases and edge conditions
49+
- Direct ETH transfer prevention
50+
51+
### Events
52+
53+
```solidity
54+
event DonationMade(
55+
address indexed recipientAddress,
56+
uint256 amount,
57+
address indexed tokenAddress,
58+
bytes data
59+
);
60+
```
61+
62+
### Custom Errors
63+
64+
```solidity
65+
error InvalidInput();
66+
error InsufficientAllowance();
67+
```
68+
69+
## Usage Examples
70+
71+
### Making a Single ETH Donation
72+
```solidity
73+
// Donate 1 ETH to a recipient
74+
donationHandler.donateETH{value: 1 ether}(
75+
recipientAddress,
76+
1 ether,
77+
"0x" // Optional data
78+
);
79+
```
80+
81+
### Making Multiple ERC20 Donations
82+
```solidity
83+
// Batch donate tokens to multiple recipients
84+
donationHandler.donateManyERC20(
85+
tokenAddress,
86+
totalAmount,
87+
recipientAddresses,
88+
amounts,
89+
data
90+
);
91+
```
92+
93+
## Technical Requirements
94+
95+
- Solidity ^0.8.0
96+
- OpenZeppelin Contracts (Upgradeable)
97+
- Foundry for testing and deployment
98+
99+
---
100+
1101
<img src="https://raw.githubusercontent.com/defi-wonderland/brand/v1.0.0/external/solidity-foundry-boilerplate-banner.png" alt="wonderland banner" align="center" />
2102
<br />
3103

foundry.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ multiline_func_header = 'params_first_multi'
99
sort_imports = true
1010

1111
[profile.default]
12-
solc_version = '0.8.23'
12+
solc = "0.8.23"
1313
libs = ['node_modules', 'lib']
1414
optimizer_runs = 10_000
1515
via_ir = true

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"scripts": {
1313
"build": "forge build",
1414
"build:optimized": "FOUNDRY_PROFILE=optimized forge build",
15-
"coverage": "forge coverage --report summary --report lcov --match-path 'test/unit/*'",
15+
"coverage": "forge coverage --ir-minimum --report summary --report lcov --match-path 'test/unit/*'",
1616
"deploy:mainnet": "bash -c 'source .env && forge script Deploy --rpc-url $MAINNET_RPC --account $MAINNET_DEPLOYER_NAME --broadcast --verify --chain mainnet -vvvvv'",
1717
"deploy:sepolia": "bash -c 'source .env && forge script Deploy --rpc-url $SEPOLIA_RPC --account $SEPOLIA_DEPLOYER_NAME --broadcast --verify --chain sepolia -vvvvv'",
1818
"lint:check": "yarn lint:sol && forge fmt --check",

src/contracts/DonationHandler.sol

+11-6
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ contract DonationHandler is OwnableUpgradeable, ReentrancyGuardUpgradeable {
1414
/// @param recipientAddress The address of the recipient of the donation
1515
/// @param amount The amount of the donation
1616
/// @param tokenAddress The address of the token being donated
17-
event DonationMade(address indexed recipientAddress, uint256 amount, address indexed tokenAddress);
17+
/// @param data The data of the donation, including projectId
18+
event DonationMade(address indexed recipientAddress, uint256 amount, address indexed tokenAddress, bytes data);
1819

1920
// Custom errors
2021
/// @notice Error emitted when the input is invalid
@@ -67,6 +68,7 @@ contract DonationHandler is OwnableUpgradeable, ReentrancyGuardUpgradeable {
6768
/// @param requiredAmount The required amount of the allowance
6869
/// @param owner The owner of the token
6970
modifier checkERC20Allowance(address tokenAddress, uint256 requiredAmount, address owner) {
71+
require(tokenAddress != ETH_TOKEN_ADDRESS, 'Invalid token address');
7072
uint256 allowance = IERC20(tokenAddress).allowance(owner, address(this));
7173
if (allowance < requiredAmount) {
7274
revert InsufficientAllowance();
@@ -106,6 +108,7 @@ contract DonationHandler is OwnableUpgradeable, ReentrancyGuardUpgradeable {
106108
/// @param amount The amount of the donation
107109
/// @param data The data of the donation
108110
function donateETH(address recipientAddress, uint256 amount, bytes calldata data) external payable nonReentrant {
111+
require(msg.value == amount, 'Incorrect ETH amount sent');
109112
_handleETH(amount, recipientAddress, data);
110113
}
111114

@@ -155,33 +158,35 @@ contract DonationHandler is OwnableUpgradeable, ReentrancyGuardUpgradeable {
155158
uint256 amount,
156159
bytes calldata data
157160
) external nonReentrant checkERC20Allowance(tokenAddress, amount, msg.sender) {
158-
require(tokenAddress != ETH_TOKEN_ADDRESS, 'Invalid token address');
159161
_handleERC20(tokenAddress, amount, recipientAddress, data);
160162
}
161163

162164
// Internal functions
163165
/// @notice Handle a single ETH donation
164166
/// @param amount The amount of the donation
165167
/// @param recipientAddress The address of the recipient of the donation
166-
function _handleETH(uint256 amount, address recipientAddress, bytes memory) internal {
168+
function _handleETH(uint256 amount, address recipientAddress, bytes memory data) internal {
167169
// Interactions
170+
171+
if (recipientAddress == ETH_TOKEN_ADDRESS) revert InvalidInput();
172+
if (amount == 0) revert InvalidInput();
168173
(bool success,) = recipientAddress.call{value: amount}('');
169174
require(success, 'ETH transfer failed');
170175
// Effects
171-
emit DonationMade(recipientAddress, amount, ETH_TOKEN_ADDRESS);
176+
emit DonationMade(recipientAddress, amount, ETH_TOKEN_ADDRESS, data);
172177
}
173178

174179
/// @notice Handle a single ERC20 donation
175180
/// @param token The address of the token being donated
176181
/// @param amount The amount of the donation
177182
/// @param recipientAddress The address of the recipient of the donation
178-
function _handleERC20(address token, uint256 amount, address recipientAddress, bytes memory) internal {
183+
function _handleERC20(address token, uint256 amount, address recipientAddress, bytes memory data) internal {
179184
if (token == address(0) || recipientAddress == address(0)) revert InvalidInput();
180185
if (amount == 0) revert InvalidInput();
181186
bool success = IERC20(token).transferFrom(msg.sender, recipientAddress, amount);
182187
require(success, 'ERC20 transfer failed');
183188

184-
emit DonationMade(recipientAddress, amount, token);
189+
emit DonationMade(recipientAddress, amount, token, data);
185190
}
186191

187192
// Receive function to accept ETH

0 commit comments

Comments
 (0)